1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-09 05:00:10 +00:00

Merge main

This commit is contained in:
Bernd Schoolmann
2026-01-13 14:03:30 +01:00
605 changed files with 32014 additions and 5329 deletions

View File

@@ -9,9 +9,7 @@ import {
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { OrganizationId } from "@bitwarden/common/types/guid";
@@ -28,7 +26,6 @@ export class ConfirmCommand {
private encryptService: EncryptService,
private organizationUserApiService: OrganizationUserApiService,
private accountService: AccountService,
private configService: ConfigService,
private i18nService: I18nService,
) {}
@@ -80,11 +77,7 @@ export class ConfirmCommand {
const key = await this.encryptService.encapsulateKeyUnsigned(orgKey, publicKey);
const req = new OrganizationUserConfirmRequest();
req.key = key;
if (
await firstValueFrom(this.configService.getFeatureFlag$(FeatureFlag.CreateDefaultLocation))
) {
req.defaultUserCollectionName = await this.getEncryptedDefaultUserCollectionName(orgKey);
}
req.defaultUserCollectionName = await this.getEncryptedDefaultUserCollectionName(orgKey);
await this.organizationUserApiService.postOrganizationUserConfirm(
options.organizationId,
id,

View File

@@ -172,9 +172,7 @@ export abstract class BaseProgram {
} else {
const command = new UnlockCommand(
this.serviceContainer.accountService,
this.serviceContainer.masterPasswordService,
this.serviceContainer.keyService,
this.serviceContainer.userVerificationService,
this.serviceContainer.cryptoFunctionService,
this.serviceContainer.logService,
this.serviceContainer.keyConnectorService,
@@ -184,7 +182,6 @@ export abstract class BaseProgram {
this.serviceContainer.i18nService,
this.serviceContainer.encryptedMigrator,
this.serviceContainer.masterPasswordUnlockService,
this.serviceContainer.configService,
);
const response = await command.run(null, null);
if (!response.success) {

View File

@@ -3,21 +3,16 @@ import { of } from "rxjs";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
import { MasterPasswordVerificationResponse } from "@bitwarden/common/auth/types/verification";
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
import { EncryptedMigrator } from "@bitwarden/common/key-management/encrypted-migrator/encrypted-migrator.abstraction";
import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service";
import { MasterPasswordUnlockService } from "@bitwarden/common/key-management/master-password/abstractions/master-password-unlock.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { mockAccountInfoWith } from "@bitwarden/common/spec";
import { CsprngArray } from "@bitwarden/common/types/csprng";
import { MasterKey, UserKey } from "@bitwarden/common/types/key";
import { UserKey } from "@bitwarden/common/types/key";
import { KeyService } from "@bitwarden/key-management";
import { ConsoleLogService } from "@bitwarden/logging";
import { UserId } from "@bitwarden/user-core";
@@ -32,9 +27,7 @@ describe("UnlockCommand", () => {
let command: UnlockCommand;
const accountService = mock<AccountService>();
const masterPasswordService = mock<InternalMasterPasswordServiceAbstraction>();
const keyService = mock<KeyService>();
const userVerificationService = mock<UserVerificationService>();
const cryptoFunctionService = mock<CryptoFunctionService>();
const logService = mock<ConsoleLogService>();
const keyConnectorService = mock<KeyConnectorService>();
@@ -44,7 +37,6 @@ describe("UnlockCommand", () => {
const i18nService = mock<I18nService>();
const encryptedMigrator = mock<EncryptedMigrator>();
const masterPasswordUnlockService = mock<MasterPasswordUnlockService>();
const configService = mock<ConfigService>();
const mockMasterPassword = "testExample";
const activeAccount: Account = {
@@ -73,9 +65,6 @@ describe("UnlockCommand", () => {
);
expectedSuccessMessage.raw = b64sessionKey;
// Legacy test data
const mockMasterKey = new SymmetricCryptoKey(new Uint8Array(32)) as MasterKey;
beforeEach(async () => {
jest.clearAllMocks();
@@ -86,9 +75,7 @@ describe("UnlockCommand", () => {
command = new UnlockCommand(
accountService,
masterPasswordService,
keyService,
userVerificationService,
cryptoFunctionService,
logService,
keyConnectorService,
@@ -98,7 +85,6 @@ describe("UnlockCommand", () => {
i18nService,
encryptedMigrator,
masterPasswordUnlockService,
configService,
);
});
@@ -133,116 +119,46 @@ describe("UnlockCommand", () => {
},
);
describe("UnlockWithMasterPasswordUnlockData feature flag enabled", () => {
beforeEach(() => {
configService.getFeatureFlag$.mockReturnValue(of(true));
});
it("calls masterPasswordUnlockService successfully", async () => {
masterPasswordUnlockService.unlockWithMasterPassword.mockResolvedValue(mockUserKey);
it("calls masterPasswordUnlockService successfully", async () => {
masterPasswordUnlockService.unlockWithMasterPassword.mockResolvedValue(mockUserKey);
const response = await command.run(mockMasterPassword, {});
const response = await command.run(mockMasterPassword, {});
expect(response).not.toBeNull();
expect(response.success).toEqual(true);
expect(response.data).toEqual(expectedSuccessMessage);
expect(masterPasswordUnlockService.unlockWithMasterPassword).toHaveBeenCalledWith(
mockMasterPassword,
activeAccount.id,
);
expect(keyService.setUserKey).toHaveBeenCalledWith(mockUserKey, activeAccount.id);
});
it("returns error response if unlockWithMasterPassword fails", async () => {
masterPasswordUnlockService.unlockWithMasterPassword.mockRejectedValue(
new Error("Unlock failed"),
);
const response = await command.run(mockMasterPassword, {});
expect(response).not.toBeNull();
expect(response.success).toEqual(false);
expect(response.message).toEqual("Unlock failed");
expect(masterPasswordUnlockService.unlockWithMasterPassword).toHaveBeenCalledWith(
mockMasterPassword,
activeAccount.id,
);
expect(keyService.setUserKey).not.toHaveBeenCalled();
});
expect(response).not.toBeNull();
expect(response.success).toEqual(true);
expect(response.data).toEqual(expectedSuccessMessage);
expect(masterPasswordUnlockService.unlockWithMasterPassword).toHaveBeenCalledWith(
mockMasterPassword,
activeAccount.id,
);
expect(keyService.setUserKey).toHaveBeenCalledWith(mockUserKey, activeAccount.id);
});
describe("unlock with feature flag off", () => {
beforeEach(() => {
configService.getFeatureFlag$.mockReturnValue(of(false));
});
it("returns error response if unlockWithMasterPassword fails", async () => {
masterPasswordUnlockService.unlockWithMasterPassword.mockRejectedValue(
new Error("Unlock failed"),
);
it("calls decryptUserKeyWithMasterKey successfully", async () => {
userVerificationService.verifyUserByMasterPassword.mockResolvedValue({
masterKey: mockMasterKey,
} as MasterPasswordVerificationResponse);
masterPasswordService.decryptUserKeyWithMasterKey.mockResolvedValue(mockUserKey);
const response = await command.run(mockMasterPassword, {});
const response = await command.run(mockMasterPassword, {});
expect(response).not.toBeNull();
expect(response.success).toEqual(true);
expect(response.data).toEqual(expectedSuccessMessage);
expect(userVerificationService.verifyUserByMasterPassword).toHaveBeenCalledWith(
{
type: VerificationType.MasterPassword,
secret: mockMasterPassword,
},
activeAccount.id,
activeAccount.email,
);
expect(masterPasswordService.decryptUserKeyWithMasterKey).toHaveBeenCalledWith(
mockMasterKey,
activeAccount.id,
);
expect(keyService.setUserKey).toHaveBeenCalledWith(mockUserKey, activeAccount.id);
});
it("returns error response when verifyUserByMasterPassword throws", async () => {
userVerificationService.verifyUserByMasterPassword.mockRejectedValue(
new Error("Verification failed"),
);
const response = await command.run(mockMasterPassword, {});
expect(response).not.toBeNull();
expect(response.success).toEqual(false);
expect(response.message).toEqual("Verification failed");
expect(userVerificationService.verifyUserByMasterPassword).toHaveBeenCalledWith(
{
type: VerificationType.MasterPassword,
secret: mockMasterPassword,
},
activeAccount.id,
activeAccount.email,
);
expect(masterPasswordService.decryptUserKeyWithMasterKey).not.toHaveBeenCalled();
expect(keyService.setUserKey).not.toHaveBeenCalled();
});
expect(response).not.toBeNull();
expect(response.success).toEqual(false);
expect(response.message).toEqual("Unlock failed");
expect(masterPasswordUnlockService.unlockWithMasterPassword).toHaveBeenCalledWith(
mockMasterPassword,
activeAccount.id,
);
expect(keyService.setUserKey).not.toHaveBeenCalled();
});
describe("calls convertToKeyConnectorCommand if required", () => {
let convertToKeyConnectorSpy: jest.SpyInstance;
beforeEach(() => {
keyConnectorService.convertAccountRequired$ = of(true);
// Feature flag on
masterPasswordUnlockService.unlockWithMasterPassword.mockResolvedValue(mockUserKey);
// Feature flag off
userVerificationService.verifyUserByMasterPassword.mockResolvedValue({
masterKey: mockMasterKey,
} as MasterPasswordVerificationResponse);
masterPasswordService.decryptUserKeyWithMasterKey.mockResolvedValue(mockUserKey);
});
test.each([true, false])("returns failure when feature flag is %s", async (flagValue) => {
configService.getFeatureFlag$.mockReturnValue(of(flagValue));
it("returns error on failure", async () => {
// Mock the ConvertToKeyConnectorCommand
const mockRun = jest.fn().mockResolvedValue({ success: false, message: "convert failed" });
convertToKeyConnectorSpy = jest
@@ -257,67 +173,32 @@ describe("UnlockCommand", () => {
expect(keyService.setUserKey).toHaveBeenCalledWith(mockUserKey, activeAccount.id);
expect(convertToKeyConnectorSpy).toHaveBeenCalled();
if (flagValue === true) {
expect(masterPasswordUnlockService.unlockWithMasterPassword).toHaveBeenCalledWith(
mockMasterPassword,
activeAccount.id,
);
} else {
expect(userVerificationService.verifyUserByMasterPassword).toHaveBeenCalledWith(
{
type: VerificationType.MasterPassword,
secret: mockMasterPassword,
},
activeAccount.id,
activeAccount.email,
);
expect(masterPasswordService.decryptUserKeyWithMasterKey).toHaveBeenCalledWith(
mockMasterKey,
activeAccount.id,
);
}
expect(masterPasswordUnlockService.unlockWithMasterPassword).toHaveBeenCalledWith(
mockMasterPassword,
activeAccount.id,
);
});
test.each([true, false])(
"returns expected success when feature flag is %s",
async (flagValue) => {
configService.getFeatureFlag$.mockReturnValue(of(flagValue));
it("returns success on successful conversion", async () => {
// Mock the ConvertToKeyConnectorCommand
const mockRun = jest.fn().mockResolvedValue({ success: true });
const convertToKeyConnectorSpy = jest
.spyOn(ConvertToKeyConnectorCommand.prototype, "run")
.mockImplementation(mockRun);
// Mock the ConvertToKeyConnectorCommand
const mockRun = jest.fn().mockResolvedValue({ success: true });
const convertToKeyConnectorSpy = jest
.spyOn(ConvertToKeyConnectorCommand.prototype, "run")
.mockImplementation(mockRun);
const response = await command.run(mockMasterPassword, {});
const response = await command.run(mockMasterPassword, {});
expect(response).not.toBeNull();
expect(response.success).toEqual(true);
expect(response.data).toEqual(expectedSuccessMessage);
expect(keyService.setUserKey).toHaveBeenCalledWith(mockUserKey, activeAccount.id);
expect(convertToKeyConnectorSpy).toHaveBeenCalled();
expect(response).not.toBeNull();
expect(response.success).toEqual(true);
expect(response.data).toEqual(expectedSuccessMessage);
expect(keyService.setUserKey).toHaveBeenCalledWith(mockUserKey, activeAccount.id);
expect(convertToKeyConnectorSpy).toHaveBeenCalled();
if (flagValue === true) {
expect(masterPasswordUnlockService.unlockWithMasterPassword).toHaveBeenCalledWith(
mockMasterPassword,
activeAccount.id,
);
} else {
expect(userVerificationService.verifyUserByMasterPassword).toHaveBeenCalledWith(
{
type: VerificationType.MasterPassword,
secret: mockMasterPassword,
},
activeAccount.id,
activeAccount.email,
);
expect(masterPasswordService.decryptUserKeyWithMasterKey).toHaveBeenCalledWith(
mockMasterKey,
activeAccount.id,
);
}
},
);
expect(masterPasswordUnlockService.unlockWithMasterPassword).toHaveBeenCalledWith(
mockMasterPassword,
activeAccount.id,
);
});
});
});
});

View File

@@ -4,20 +4,13 @@ import { firstValueFrom } from "rxjs";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { VerificationType } from "@bitwarden/common/auth/enums/verification-type";
import { MasterPasswordVerification } from "@bitwarden/common/auth/types/verification";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
import { EncryptedMigrator } from "@bitwarden/common/key-management/encrypted-migrator/encrypted-migrator.abstraction";
import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service";
import { MasterPasswordUnlockService } from "@bitwarden/common/key-management/master-password/abstractions/master-password-unlock.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { ConsoleLogService } from "@bitwarden/common/platform/services/console-log.service";
import { MasterKey } from "@bitwarden/common/types/key";
import { KeyService } from "@bitwarden/key-management";
import { Response } from "../../models/response";
@@ -29,9 +22,7 @@ import { ConvertToKeyConnectorCommand } from "../convert-to-key-connector.comman
export class UnlockCommand {
constructor(
private accountService: AccountService,
private masterPasswordService: InternalMasterPasswordServiceAbstraction,
private keyService: KeyService,
private userVerificationService: UserVerificationService,
private cryptoFunctionService: CryptoFunctionService,
private logService: ConsoleLogService,
private keyConnectorService: KeyConnectorService,
@@ -41,7 +32,6 @@ export class UnlockCommand {
private i18nService: I18nService,
private encryptedMigrator: EncryptedMigrator,
private masterPasswordUnlockService: MasterPasswordUnlockService,
private configService: ConfigService,
) {}
async run(password: string, cmdOptions: Record<string, any>) {
@@ -61,46 +51,15 @@ export class UnlockCommand {
}
const userId = activeAccount.id;
if (
await firstValueFrom(
this.configService.getFeatureFlag$(FeatureFlag.UnlockWithMasterPasswordUnlockData),
)
) {
try {
const userKey = await this.masterPasswordUnlockService.unlockWithMasterPassword(
password,
userId,
);
await this.keyService.setUserKey(userKey, userId);
} catch (e) {
return Response.error(e.message);
}
} else {
const email = activeAccount.email;
const verification = {
type: VerificationType.MasterPassword,
secret: password,
} as MasterPasswordVerification;
let masterKey: MasterKey;
try {
const response = await this.userVerificationService.verifyUserByMasterPassword(
verification,
userId,
email,
);
masterKey = response.masterKey;
} catch (e) {
// verification failure throws
return Response.error(e.message);
}
const userKey = await this.masterPasswordService.decryptUserKeyWithMasterKey(
masterKey,
try {
const userKey = await this.masterPasswordUnlockService.unlockWithMasterPassword(
password,
userId,
);
await this.keyService.setUserKey(userKey, userId);
} catch (e) {
return Response.error(e.message);
}
if (await firstValueFrom(this.keyConnectorService.convertAccountRequired$)) {

View File

@@ -147,7 +147,6 @@ export class OssServeConfigurator {
this.serviceContainer.encryptService,
this.serviceContainer.organizationUserApiService,
this.serviceContainer.accountService,
this.serviceContainer.configService,
this.serviceContainer.i18nService,
);
this.restoreCommand = new RestoreCommand(
@@ -167,9 +166,7 @@ export class OssServeConfigurator {
);
this.unlockCommand = new UnlockCommand(
this.serviceContainer.accountService,
this.serviceContainer.masterPasswordService,
this.serviceContainer.keyService,
this.serviceContainer.userVerificationService,
this.serviceContainer.cryptoFunctionService,
this.serviceContainer.logService,
this.serviceContainer.keyConnectorService,
@@ -179,7 +176,6 @@ export class OssServeConfigurator {
this.serviceContainer.i18nService,
this.serviceContainer.encryptedMigrator,
this.serviceContainer.masterPasswordUnlockService,
this.serviceContainer.configService,
);
this.sendCreateCommand = new SendCreateCommand(

View File

@@ -303,9 +303,7 @@ export class Program extends BaseProgram {
await this.exitIfNotAuthed();
const command = new UnlockCommand(
this.serviceContainer.accountService,
this.serviceContainer.masterPasswordService,
this.serviceContainer.keyService,
this.serviceContainer.userVerificationService,
this.serviceContainer.cryptoFunctionService,
this.serviceContainer.logService,
this.serviceContainer.keyConnectorService,
@@ -315,7 +313,6 @@ export class Program extends BaseProgram {
this.serviceContainer.i18nService,
this.serviceContainer.encryptedMigrator,
this.serviceContainer.masterPasswordUnlockService,
this.serviceContainer.configService,
);
const response = await command.run(password, cmd);
this.processResponse(response);

View File

@@ -9,9 +9,9 @@ import { AccountService } from "@bitwarden/common/auth/abstractions/account.serv
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { SendType } from "@bitwarden/common/tools/send/types/send-type";
import { NodeUtils } from "@bitwarden/node/node-utils";
import { Response } from "../../../models/response";

View File

@@ -5,9 +5,9 @@ import { firstValueFrom } from "rxjs";
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 { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
import { SendType } from "@bitwarden/common/tools/send/types/send-type";
import { Response } from "../../../models/response";
import { CliUtils } from "../../../utils";

View File

@@ -13,11 +13,11 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { SendAccess } from "@bitwarden/common/tools/send/models/domain/send-access";
import { SendAccessRequest } from "@bitwarden/common/tools/send/models/request/send-access.request";
import { SendAccessView } from "@bitwarden/common/tools/send/models/view/send-access.view";
import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.service.abstraction";
import { SendType } from "@bitwarden/common/tools/send/types/send-type";
import { KeyService } from "@bitwarden/key-management";
import { NodeUtils } from "@bitwarden/node/node-utils";

View File

@@ -1,4 +1,4 @@
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { SendType } from "@bitwarden/common/tools/send/types/send-type";
import { Response } from "../../../models/response";
import { TemplateResponse } from "../../../models/response/template.response";

View File

@@ -1,7 +1,7 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { SendAccessView } from "@bitwarden/common/tools/send/models/view/send-access.view";
import { SendType } from "@bitwarden/common/tools/send/types/send-type";
import { BaseResponse } from "../../../models/response/base.response";

View File

@@ -1,8 +1,8 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { SendView } from "@bitwarden/common/tools/send/models/view/send.view";
import { SendType } from "@bitwarden/common/tools/send/types/send-type";
import { BaseResponse } from "../../../models/response/base.response";

View File

@@ -7,7 +7,7 @@ import * as chalk from "chalk";
import { program, Command, Option, OptionValues } from "commander";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
import { SendType } from "@bitwarden/common/tools/send/types/send-type";
import { BaseProgram } from "../../base-program";
import { Response } from "../../models/response";

View File

@@ -494,7 +494,6 @@ export class VaultProgram extends BaseProgram {
this.serviceContainer.encryptService,
this.serviceContainer.organizationUserApiService,
this.serviceContainer.accountService,
this.serviceContainer.configService,
this.serviceContainer.i18nService,
);
const response = await command.run(object, id, cmd);