1
0
mirror of https://github.com/bitwarden/browser synced 2026-03-01 11:01:17 +00:00

Merge remote-tracking branch 'origin' into auth/pm-18720/change-password-component-non-dialog-v3

This commit is contained in:
Patrick Pimentel
2025-06-10 16:51:21 -06:00
521 changed files with 7401 additions and 6359 deletions

View File

@@ -44,7 +44,6 @@ export interface AnonLayoutWrapperData {
}
@Component({
standalone: true,
templateUrl: "anon-layout-wrapper.component.html",
imports: [AnonLayoutComponent, RouterModule],
})

View File

@@ -19,7 +19,6 @@ import { TypographyModule } from "../../../../components/src/typography";
import { BitwardenLogo, BitwardenShield } from "../icons";
@Component({
standalone: true,
selector: "auth-anon-layout",
templateUrl: "./anon-layout.component.html",
imports: [IconModule, CommonModule, TypographyModule, SharedModule, RouterModule],

View File

@@ -35,7 +35,6 @@ import { ChangePasswordService } from "./change-password.service.abstraction";
* end up at a change password without having one before.
*/
@Component({
standalone: true,
selector: "auth-change-password",
templateUrl: "change-password.component.html",
imports: [InputPasswordComponent, I18nPipe],

View File

@@ -11,7 +11,6 @@ export type FingerprintDialogData = {
@Component({
templateUrl: "fingerprint-dialog.component.html",
standalone: true,
imports: [JslibModule, ButtonModule, DialogModule],
})
export class FingerprintDialogComponent {

View File

@@ -83,7 +83,6 @@ interface InputPasswordForm {
}
@Component({
standalone: true,
selector: "auth-input-password",
templateUrl: "./input-password.component.html",
imports: [

View File

@@ -40,7 +40,6 @@ export interface LoginApprovalDialogParams {
@Component({
selector: "login-approval",
templateUrl: "login-approval.component.html",
standalone: true,
imports: [CommonModule, AsyncActionsModule, ButtonModule, DialogModule, JslibModule],
})
export class LoginApprovalComponent implements OnInit, OnDestroy {
@@ -101,6 +100,8 @@ export class LoginApprovalComponent implements OnInit, OnDestroy {
this.updateTimeText();
}, RequestTimeUpdate);
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.loginApprovalComponentService.showLoginRequestedAlertIfWindowNotVisible(this.email);
this.loading = false;

View File

@@ -51,7 +51,6 @@ enum State {
}
@Component({
standalone: true,
templateUrl: "./login-decryption-options.component.html",
imports: [
AsyncActionsModule,

View File

@@ -57,7 +57,6 @@ const matchOptions: IsActiveMatchOptions = {
};
@Component({
standalone: true,
templateUrl: "./login-via-auth-request.component.html",
imports: [ButtonModule, CommonModule, JslibModule, LinkModule, RouterModule],
providers: [{ provide: LoginViaAuthRequestCacheService }],

View File

@@ -9,7 +9,6 @@ import { DefaultServerSettingsService } from "@bitwarden/common/platform/service
import { LinkModule } from "@bitwarden/components";
@Component({
standalone: true,
imports: [CommonModule, JslibModule, LinkModule, RouterModule],
template: `
<div class="tw-text-center" *ngIf="!(isUserRegistrationDisabled$ | async)">

View File

@@ -58,7 +58,6 @@ export enum LoginUiState {
}
@Component({
standalone: true,
templateUrl: "./login.component.html",
imports: [
AsyncActionsModule,

View File

@@ -25,7 +25,6 @@ import { LoginStrategyServiceAbstraction } from "../../common/abstractions/login
* Component for verifying a new device via a one-time password (OTP).
*/
@Component({
standalone: true,
selector: "app-new-device-verification",
templateUrl: "./new-device-verification.component.html",
imports: [
@@ -138,6 +137,8 @@ export class NewDeviceVerificationComponent implements OnInit, OnDestroy {
return;
}
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.loginSuccessHandlerService.run(authResult.userId);
// If verification succeeds, navigate to vault

View File

@@ -13,7 +13,6 @@ import { CalloutModule } from "@bitwarden/components";
@Component({
selector: "auth-password-callout",
templateUrl: "password-callout.component.html",
standalone: true,
imports: [CommonModule, JslibModule, CalloutModule],
})
export class PasswordCalloutComponent {

View File

@@ -23,7 +23,6 @@ import {
} from "@bitwarden/components";
@Component({
standalone: true,
templateUrl: "./password-hint.component.html",
imports: [
AsyncActionsModule,

View File

@@ -26,7 +26,6 @@ import { SelfHostedEnvConfigDialogComponent } from "../../self-hosted-env-config
* Outputs the selected region to the parent component so it can respond as necessary.
*/
@Component({
standalone: true,
selector: "auth-registration-env-selector",
templateUrl: "registration-env-selector.component.html",
imports: [CommonModule, JslibModule, ReactiveFormsModule, FormFieldModule, SelectModule],

View File

@@ -33,7 +33,6 @@ import { PasswordInputResult } from "../../input-password/password-input-result"
import { RegistrationFinishService } from "./registration-finish.service";
@Component({
standalone: true,
selector: "auth-registration-finish",
templateUrl: "./registration-finish.component.html",
imports: [CommonModule, JslibModule, RouterModule, InputPasswordComponent],

View File

@@ -21,7 +21,6 @@ export interface RegistrationLinkExpiredComponentData {
}
@Component({
standalone: true,
selector: "auth-registration-link-expired",
templateUrl: "./registration-link-expired.component.html",
imports: [CommonModule, JslibModule, RouterModule, IconModule, ButtonModule],

View File

@@ -19,7 +19,6 @@ export interface RegistrationStartSecondaryComponentData {
}
@Component({
standalone: true,
selector: "auth-registration-start-secondary",
templateUrl: "./registration-start-secondary.component.html",
imports: [CommonModule, JslibModule, RouterModule, LinkModule],

View File

@@ -42,7 +42,6 @@ const DEFAULT_MARKETING_EMAILS_PREF_BY_REGION: Record<Region, boolean> = {
};
@Component({
standalone: true,
selector: "auth-registration-start",
templateUrl: "./registration-start.component.html",
imports: [

View File

@@ -55,7 +55,6 @@ function selfHostedEnvSettingsFormValidator(): ValidatorFn {
* Dialog for configuring self-hosted environment settings.
*/
@Component({
standalone: true,
selector: "self-hosted-env-config-dialog",
templateUrl: "self-hosted-env-config-dialog.component.html",
imports: [

View File

@@ -30,7 +30,6 @@ import {
} from "./set-password-jit.service.abstraction";
@Component({
standalone: true,
selector: "auth-set-password-jit",
templateUrl: "set-password-jit.component.html",
imports: [CommonModule, InputPasswordComponent, JslibModule],

View File

@@ -62,7 +62,6 @@ interface QueryParams {
* This component handles the SSO flow.
*/
@Component({
standalone: true,
templateUrl: "sso.component.html",
imports: [
AsyncActionsModule,

View File

@@ -15,7 +15,6 @@ import {
} from "@bitwarden/components";
@Component({
standalone: true,
selector: "app-two-factor-auth-authenticator",
templateUrl: "two-factor-auth-authenticator.component.html",
imports: [

View File

@@ -26,7 +26,6 @@ import {
} from "./two-factor-auth-duo-component.service";
@Component({
standalone: true,
selector: "app-two-factor-auth-duo",
template: "",
imports: [

View File

@@ -28,7 +28,6 @@ import { TwoFactorAuthEmailComponentCacheService } from "./two-factor-auth-email
import { TwoFactorAuthEmailComponentService } from "./two-factor-auth-email-component.service";
@Component({
standalone: true,
selector: "app-two-factor-auth-email",
templateUrl: "two-factor-auth-email.component.html",
imports: [

View File

@@ -33,7 +33,6 @@ export interface WebAuthnResult {
}
@Component({
standalone: true,
selector: "app-two-factor-auth-webauthn",
templateUrl: "two-factor-auth-webauthn.component.html",
imports: [

View File

@@ -15,7 +15,6 @@ import {
} from "@bitwarden/components";
@Component({
standalone: true,
selector: "app-two-factor-auth-yubikey",
templateUrl: "two-factor-auth-yubikey.component.html",
imports: [

View File

@@ -77,7 +77,6 @@ import {
} from "./two-factor-options.component";
@Component({
standalone: true,
selector: "app-two-factor-auth",
templateUrl: "two-factor-auth.component.html",
imports: [
@@ -266,6 +265,8 @@ export class TwoFactorAuthComponent implements OnInit, OnDestroy {
private listenForAuthnSessionTimeout() {
this.loginStrategyService.authenticationSessionTimeout$
.pipe(takeUntilDestroyed(this.destroyRef))
// TODO: Fix this!
// eslint-disable-next-line rxjs/no-async-subscribe
.subscribe(async (expired) => {
if (!expired) {
return;

View File

@@ -32,7 +32,6 @@ export type TwoFactorOptionsDialogResult = {
};
@Component({
standalone: true,
selector: "app-two-factor-options",
templateUrl: "two-factor-options.component.html",
imports: [

View File

@@ -32,7 +32,6 @@ import { UserVerificationFormInputComponent } from "./user-verification-form-inp
@Component({
templateUrl: "user-verification-dialog.component.html",
standalone: true,
imports: [
CommonModule,
ReactiveFormsModule,

View File

@@ -56,7 +56,6 @@ import { ActiveClientVerificationOption } from "./active-client-verification-opt
transition(":enter", [style({ opacity: 0 }), animate("100ms", style({ opacity: 1 }))]),
]),
],
standalone: true,
imports: [
CommonModule,
ReactiveFormsModule,

View File

@@ -47,7 +47,6 @@ type VaultTimeoutFormValue = VaultTimeoutForm["value"];
@Component({
selector: "auth-vault-timeout-input",
templateUrl: "vault-timeout-input.component.html",
standalone: true,
imports: [CommonModule, JslibModule, ReactiveFormsModule, FormFieldModule, SelectModule],
providers: [
{

View File

@@ -105,23 +105,6 @@ describe("AuthRequestService", () => {
);
});
it("should use the master key and hash if they exist", async () => {
masterPasswordService.masterKeySubject.next(
new SymmetricCryptoKey(new Uint8Array(32)) as MasterKey,
);
masterPasswordService.masterKeyHashSubject.next("MASTER_KEY_HASH");
await sut.approveOrDenyAuthRequest(
true,
new AuthRequestResponse({ id: "123", publicKey: "KEY" }),
);
expect(encryptService.encapsulateKeyUnsigned).toHaveBeenCalledWith(
new SymmetricCryptoKey(new Uint8Array(32)),
expect.anything(),
);
});
it("should use the user key if the master key and hash do not exist", async () => {
keyService.getUserKey.mockResolvedValueOnce(
new SymmetricCryptoKey(new Uint8Array(64)) as UserKey,
@@ -246,45 +229,6 @@ describe("AuthRequestService", () => {
});
});
describe("decryptAuthReqPubKeyEncryptedMasterKeyAndHash", () => {
it("returns a decrypted master key and hash when given a valid public key encrypted master key, public key encrypted master key hash, and an auth req private key", async () => {
// Arrange
const mockPubKeyEncryptedMasterKey = "pubKeyEncryptedMasterKey";
const mockPubKeyEncryptedMasterKeyHash = "pubKeyEncryptedMasterKeyHash";
const mockDecryptedMasterKeyBytes = new Uint8Array(64);
const mockDecryptedMasterKey = new SymmetricCryptoKey(
mockDecryptedMasterKeyBytes,
) as MasterKey;
const mockDecryptedMasterKeyHashBytes = new Uint8Array(64);
const mockDecryptedMasterKeyHash = Utils.fromBufferToUtf8(mockDecryptedMasterKeyHashBytes);
encryptService.rsaDecrypt.mockResolvedValueOnce(mockDecryptedMasterKeyHashBytes);
encryptService.decapsulateKeyUnsigned.mockResolvedValueOnce(
new SymmetricCryptoKey(mockDecryptedMasterKeyBytes),
);
// Act
const result = await sut.decryptPubKeyEncryptedMasterKeyAndHash(
mockPubKeyEncryptedMasterKey,
mockPubKeyEncryptedMasterKeyHash,
mockPrivateKey,
);
// Assert
expect(encryptService.decapsulateKeyUnsigned).toHaveBeenCalledWith(
new EncString(mockPubKeyEncryptedMasterKey),
mockPrivateKey,
);
expect(encryptService.rsaDecrypt).toHaveBeenCalledWith(
new EncString(mockPubKeyEncryptedMasterKeyHash),
mockPrivateKey,
);
expect(result.masterKey).toEqual(mockDecryptedMasterKey);
expect(result.masterKeyHash).toEqual(mockDecryptedMasterKeyHash);
});
});
describe("getFingerprintPhrase", () => {
it("returns the same fingerprint regardless of email casing", () => {
const email = "test@email.com";

View File

@@ -103,32 +103,12 @@ export class AuthRequestService implements AuthRequestServiceAbstraction {
}
const pubKey = Utils.fromB64ToArray(authRequest.publicKey);
const userId = (await firstValueFrom(this.accountService.activeAccount$)).id;
const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
const masterKeyHash = await firstValueFrom(this.masterPasswordService.masterKeyHash$(userId));
let encryptedMasterKeyHash;
let keyToEncrypt;
if (masterKey && masterKeyHash) {
// Only encrypt the master password hash if masterKey exists as
// we won't have a masterKeyHash without a masterKey
encryptedMasterKeyHash = await this.encryptService.rsaEncrypt(
Utils.fromUtf8ToArray(masterKeyHash),
pubKey,
);
keyToEncrypt = masterKey;
} else {
keyToEncrypt = await this.keyService.getUserKey();
}
const encryptedKey = await this.encryptService.encapsulateKeyUnsigned(
keyToEncrypt as SymmetricCryptoKey,
pubKey,
);
const keyToEncrypt = await this.keyService.getUserKey();
const encryptedKey = await this.encryptService.encapsulateKeyUnsigned(keyToEncrypt, pubKey);
const response = new PasswordlessAuthRequest(
encryptedKey.encryptedString,
encryptedMasterKeyHash?.encryptedString,
undefined,
await this.appIdService.getAppId(),
approve,
);
@@ -173,10 +153,12 @@ export class AuthRequestService implements AuthRequestServiceAbstraction {
pubKeyEncryptedUserKey: string,
privateKey: Uint8Array,
): Promise<UserKey> {
return (await this.encryptService.decapsulateKeyUnsigned(
const decryptedUserKey = await this.encryptService.decapsulateKeyUnsigned(
new EncString(pubKeyEncryptedUserKey),
privateKey,
)) as UserKey;
);
return decryptedUserKey as UserKey;
}
async decryptPubKeyEncryptedMasterKeyAndHash(
@@ -184,15 +166,17 @@ export class AuthRequestService implements AuthRequestServiceAbstraction {
pubKeyEncryptedMasterKeyHash: string,
privateKey: Uint8Array,
): Promise<{ masterKey: MasterKey; masterKeyHash: string }> {
const masterKey = (await this.encryptService.decapsulateKeyUnsigned(
const decryptedMasterKeyArrayBuffer = await this.encryptService.rsaDecrypt(
new EncString(pubKeyEncryptedMasterKey),
privateKey,
)) as MasterKey;
);
const decryptedMasterKeyHashArrayBuffer = await this.encryptService.rsaDecrypt(
new EncString(pubKeyEncryptedMasterKeyHash),
privateKey,
);
const masterKey = new SymmetricCryptoKey(decryptedMasterKeyArrayBuffer) as MasterKey;
const masterKeyHash = Utils.fromBufferToUtf8(decryptedMasterKeyHashArrayBuffer);
return {