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:
@@ -44,7 +44,6 @@ export interface AnonLayoutWrapperData {
|
||||
}
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
templateUrl: "anon-layout-wrapper.component.html",
|
||||
imports: [AnonLayoutComponent, RouterModule],
|
||||
})
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -11,7 +11,6 @@ export type FingerprintDialogData = {
|
||||
|
||||
@Component({
|
||||
templateUrl: "fingerprint-dialog.component.html",
|
||||
standalone: true,
|
||||
imports: [JslibModule, ButtonModule, DialogModule],
|
||||
})
|
||||
export class FingerprintDialogComponent {
|
||||
|
||||
@@ -83,7 +83,6 @@ interface InputPasswordForm {
|
||||
}
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: "auth-input-password",
|
||||
templateUrl: "./input-password.component.html",
|
||||
imports: [
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -51,7 +51,6 @@ enum State {
|
||||
}
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
templateUrl: "./login-decryption-options.component.html",
|
||||
imports: [
|
||||
AsyncActionsModule,
|
||||
|
||||
@@ -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 }],
|
||||
|
||||
@@ -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)">
|
||||
|
||||
@@ -58,7 +58,6 @@ export enum LoginUiState {
|
||||
}
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
templateUrl: "./login.component.html",
|
||||
imports: [
|
||||
AsyncActionsModule,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -23,7 +23,6 @@ import {
|
||||
} from "@bitwarden/components";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
templateUrl: "./password-hint.component.html",
|
||||
imports: [
|
||||
AsyncActionsModule,
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -62,7 +62,6 @@ interface QueryParams {
|
||||
* This component handles the SSO flow.
|
||||
*/
|
||||
@Component({
|
||||
standalone: true,
|
||||
templateUrl: "sso.component.html",
|
||||
imports: [
|
||||
AsyncActionsModule,
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -26,7 +26,6 @@ import {
|
||||
} from "./two-factor-auth-duo-component.service";
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: "app-two-factor-auth-duo",
|
||||
template: "",
|
||||
imports: [
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -33,7 +33,6 @@ export interface WebAuthnResult {
|
||||
}
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: "app-two-factor-auth-webauthn",
|
||||
templateUrl: "two-factor-auth-webauthn.component.html",
|
||||
imports: [
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -32,7 +32,6 @@ export type TwoFactorOptionsDialogResult = {
|
||||
};
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: "app-two-factor-options",
|
||||
templateUrl: "two-factor-options.component.html",
|
||||
imports: [
|
||||
|
||||
@@ -32,7 +32,6 @@ import { UserVerificationFormInputComponent } from "./user-verification-form-inp
|
||||
|
||||
@Component({
|
||||
templateUrl: "user-verification-dialog.component.html",
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
ReactiveFormsModule,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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: [
|
||||
{
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user