mirror of
https://github.com/bitwarden/browser
synced 2026-02-09 21:20:27 +00:00
Merge branch 'main' into ac/pm-19814/improved-warning-popup-ui
This commit is contained in:
@@ -400,12 +400,6 @@ export class LoginComponent implements OnInit, OnDestroy {
|
||||
await this.router.navigate(["/login-with-device"]);
|
||||
}
|
||||
|
||||
protected async emailIsValid(): Promise<boolean> {
|
||||
this.formGroup.controls.email.markAsTouched();
|
||||
this.formGroup.controls.email.updateValueAndValidity({ onlySelf: true, emitEvent: true });
|
||||
return this.formGroup.controls.email.valid;
|
||||
}
|
||||
|
||||
protected async toggleLoginUiState(value: LoginUiState): Promise<void> {
|
||||
this.loginUiState = value;
|
||||
|
||||
@@ -474,7 +468,7 @@ export class LoginComponent implements OnInit, OnDestroy {
|
||||
* Continue to the master password entry state (only if email is validated)
|
||||
*/
|
||||
protected async continue(): Promise<void> {
|
||||
const isEmailValid = await this.emailIsValid();
|
||||
const isEmailValid = this.validateEmail();
|
||||
|
||||
if (isEmailValid) {
|
||||
await this.toggleLoginUiState(LoginUiState.MASTER_PASSWORD_ENTRY);
|
||||
@@ -496,7 +490,7 @@ export class LoginComponent implements OnInit, OnDestroy {
|
||||
*/
|
||||
async handleSsoClick() {
|
||||
// Make sure the email is valid
|
||||
const isEmailValid = await this.emailIsValid();
|
||||
const isEmailValid = this.validateEmail();
|
||||
if (!isEmailValid) {
|
||||
return;
|
||||
}
|
||||
@@ -594,11 +588,21 @@ export class LoginComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates the email and displays any validation errors.
|
||||
* @returns true if the email is valid, false otherwise.
|
||||
*/
|
||||
protected validateEmail(): boolean {
|
||||
this.formGroup.controls.email.markAsTouched();
|
||||
this.formGroup.controls.email.updateValueAndValidity({ onlySelf: true, emitEvent: true });
|
||||
return this.formGroup.controls.email.valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist the entered email address and the user's choice to remember it to state.
|
||||
*/
|
||||
private async persistEmailIfValid(): Promise<void> {
|
||||
if (await this.emailIsValid()) {
|
||||
if (this.formGroup.controls.email.valid) {
|
||||
const email = this.formGroup.value.email;
|
||||
const rememberEmail = this.formGroup.value.rememberEmail ?? false;
|
||||
if (!email) {
|
||||
@@ -613,7 +617,7 @@ export class LoginComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the email value from the input field.
|
||||
* Set the email value from the input field and persists to state if valid.
|
||||
* We only update the form controls onSubmit instead of onBlur because we don't want to show validation errors until
|
||||
* the user submits. This is because currently our validation errors are shown below the input fields, and
|
||||
* displaying them causes the screen to "jump".
|
||||
@@ -626,7 +630,7 @@ export class LoginComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Remember Email value from the input field.
|
||||
* Set the Remember Email value from the input field and persists to state if valid.
|
||||
* We only update the form controls onSubmit instead of onBlur because we don't want to show validation errors until
|
||||
* the user submits. This is because currently our validation errors are shown below the input fields, and
|
||||
* displaying them causes the screen to "jump".
|
||||
|
||||
@@ -39,6 +39,7 @@ export enum FeatureFlag {
|
||||
PrivateKeyRegeneration = "pm-12241-private-key-regeneration",
|
||||
UserKeyRotationV2 = "userkey-rotation-v2",
|
||||
PM4154_BulkEncryptionService = "PM-4154-bulk-encryption-service",
|
||||
PM17987_BlockType0 = "pm-17987-block-type-0",
|
||||
|
||||
/* Tools */
|
||||
ItemShare = "item-share",
|
||||
@@ -123,6 +124,7 @@ export const DefaultFeatureFlagValue = {
|
||||
[FeatureFlag.PrivateKeyRegeneration]: FALSE,
|
||||
[FeatureFlag.UserKeyRotationV2]: FALSE,
|
||||
[FeatureFlag.PM4154_BulkEncryptionService]: FALSE,
|
||||
[FeatureFlag.PM17987_BlockType0]: FALSE,
|
||||
|
||||
/* Platform */
|
||||
[FeatureFlag.IpcChannelFramework]: FALSE,
|
||||
|
||||
@@ -19,10 +19,17 @@ import {
|
||||
SymmetricCryptoKey,
|
||||
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
|
||||
import {
|
||||
DefaultFeatureFlagValue,
|
||||
FeatureFlag,
|
||||
getFeatureFlagValue,
|
||||
} from "../../../enums/feature-flag.enum";
|
||||
import { ServerConfig } from "../../../platform/abstractions/config/server-config";
|
||||
import { EncryptService } from "../abstractions/encrypt.service";
|
||||
|
||||
export class EncryptServiceImplementation implements EncryptService {
|
||||
private blockType0: boolean = DefaultFeatureFlagValue[FeatureFlag.PM17987_BlockType0];
|
||||
|
||||
constructor(
|
||||
protected cryptoFunctionService: CryptoFunctionService,
|
||||
protected logService: LogService,
|
||||
@@ -31,7 +38,7 @@ export class EncryptServiceImplementation implements EncryptService {
|
||||
|
||||
// Handle updating private properties to turn on/off feature flags.
|
||||
onServerConfigChange(newConfig: ServerConfig): void {
|
||||
return;
|
||||
this.blockType0 = getFeatureFlagValue(newConfig, FeatureFlag.PM17987_BlockType0);
|
||||
}
|
||||
|
||||
async encrypt(plainValue: string | Uint8Array, key: SymmetricCryptoKey): Promise<EncString> {
|
||||
@@ -39,6 +46,12 @@ export class EncryptServiceImplementation implements EncryptService {
|
||||
throw new Error("No encryption key provided.");
|
||||
}
|
||||
|
||||
if (this.blockType0) {
|
||||
if (key.encType === EncryptionType.AesCbc256_B64 || key.key.byteLength < 64) {
|
||||
throw new Error("Type 0 encryption is not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
if (plainValue == null) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
@@ -70,6 +83,12 @@ export class EncryptServiceImplementation implements EncryptService {
|
||||
throw new Error("No encryption key provided.");
|
||||
}
|
||||
|
||||
if (this.blockType0) {
|
||||
if (key.encType === EncryptionType.AesCbc256_B64 || key.key.byteLength < 64) {
|
||||
throw new Error("Type 0 encryption is not supported.");
|
||||
}
|
||||
}
|
||||
|
||||
const innerKey = key.inner();
|
||||
if (innerKey.type === EncryptionType.AesCbc256_HmacSha256_B64) {
|
||||
const encValue = await this.aesEncrypt(plainValue, innerKey);
|
||||
|
||||
@@ -10,6 +10,8 @@ import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/sym
|
||||
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
||||
|
||||
import { makeStaticByteArray } from "../../../../spec";
|
||||
import { DefaultFeatureFlagValue, FeatureFlag } from "../../../enums/feature-flag.enum";
|
||||
import { ServerConfig } from "../../../platform/abstractions/config/server-config";
|
||||
|
||||
import { EncryptServiceImplementation } from "./encrypt.service.implementation";
|
||||
|
||||
@@ -26,17 +28,65 @@ describe("EncryptService", () => {
|
||||
encryptService = new EncryptServiceImplementation(cryptoFunctionService, logService, true);
|
||||
});
|
||||
|
||||
describe("onServerConfigChange", () => {
|
||||
const newConfig = mock<ServerConfig>();
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it("updates internal flag with default value when not present in config", () => {
|
||||
encryptService.onServerConfigChange(newConfig);
|
||||
|
||||
expect((encryptService as any).blockType0).toBe(
|
||||
DefaultFeatureFlagValue[FeatureFlag.PM17987_BlockType0],
|
||||
);
|
||||
});
|
||||
|
||||
test.each([true, false])("updates internal flag with value in config", (expectedValue) => {
|
||||
newConfig.featureStates = { [FeatureFlag.PM17987_BlockType0]: expectedValue };
|
||||
|
||||
encryptService.onServerConfigChange(newConfig);
|
||||
|
||||
expect((encryptService as any).blockType0).toBe(expectedValue);
|
||||
});
|
||||
});
|
||||
|
||||
describe("encrypt", () => {
|
||||
it("throws if no key is provided", () => {
|
||||
return expect(encryptService.encrypt(null, null)).rejects.toThrow(
|
||||
"No encryption key provided.",
|
||||
);
|
||||
});
|
||||
it("returns null if no data is provided", async () => {
|
||||
const key = mock<SymmetricCryptoKey>();
|
||||
|
||||
it("throws if type 0 key is provided with flag turned on", async () => {
|
||||
(encryptService as any).blockType0 = true;
|
||||
const key = new SymmetricCryptoKey(makeStaticByteArray(32));
|
||||
const mock32Key = mock<SymmetricCryptoKey>();
|
||||
mock32Key.key = makeStaticByteArray(32);
|
||||
|
||||
await expect(encryptService.encrypt(null!, key)).rejects.toThrow(
|
||||
"Type 0 encryption is not supported.",
|
||||
);
|
||||
await expect(encryptService.encrypt(null!, mock32Key)).rejects.toThrow(
|
||||
"Type 0 encryption is not supported.",
|
||||
);
|
||||
|
||||
const plainValue = "data";
|
||||
await expect(encryptService.encrypt(plainValue, key)).rejects.toThrow(
|
||||
"Type 0 encryption is not supported.",
|
||||
);
|
||||
await expect(encryptService.encrypt(plainValue, mock32Key)).rejects.toThrow(
|
||||
"Type 0 encryption is not supported.",
|
||||
);
|
||||
});
|
||||
|
||||
it("returns null if no data is provided with valid key", async () => {
|
||||
const key = new SymmetricCryptoKey(makeStaticByteArray(64));
|
||||
const actual = await encryptService.encrypt(null, key);
|
||||
expect(actual).toBeNull();
|
||||
});
|
||||
|
||||
it("creates an EncString for Aes256Cbc", async () => {
|
||||
const key = new SymmetricCryptoKey(makeStaticByteArray(32));
|
||||
const plainValue = "data";
|
||||
@@ -53,6 +103,7 @@ describe("EncryptService", () => {
|
||||
expect(Utils.fromB64ToArray(result.data).length).toEqual(4);
|
||||
expect(Utils.fromB64ToArray(result.iv).length).toEqual(16);
|
||||
});
|
||||
|
||||
it("creates an EncString for Aes256Cbc_HmacSha256_B64", async () => {
|
||||
const key = new SymmetricCryptoKey(makeStaticByteArray(64));
|
||||
const plainValue = "data";
|
||||
@@ -90,6 +141,21 @@ describe("EncryptService", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("throws if type 0 key provided with flag turned on", async () => {
|
||||
(encryptService as any).blockType0 = true;
|
||||
const key = new SymmetricCryptoKey(makeStaticByteArray(32));
|
||||
const mock32Key = mock<SymmetricCryptoKey>();
|
||||
mock32Key.key = makeStaticByteArray(32);
|
||||
|
||||
await expect(encryptService.encryptToBytes(plainValue, key)).rejects.toThrow(
|
||||
"Type 0 encryption is not supported.",
|
||||
);
|
||||
|
||||
await expect(encryptService.encryptToBytes(plainValue, mock32Key)).rejects.toThrow(
|
||||
"Type 0 encryption is not supported.",
|
||||
);
|
||||
});
|
||||
|
||||
it("encrypts data with provided Aes256Cbc key and returns correct encbuffer", async () => {
|
||||
const key = new SymmetricCryptoKey(makeStaticByteArray(32, 0));
|
||||
const iv = makeStaticByteArray(16, 80);
|
||||
|
||||
Reference in New Issue
Block a user