1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-17 16:53:34 +00:00

feat(prelogin): [Auth/PM-23801] Move Prelogin Request (#17080)

* feat(prelogin): [PM-23801] Move Prelogin Request - Initial implementation.

* test(prelogin): [PM-23801] Move Prelogin Request - Removed unneeded test.
This commit is contained in:
Patrick-Pimentel-Bitwarden
2025-11-10 10:54:25 -05:00
committed by GitHub
parent f7899991a0
commit 5aa6d38d80
9 changed files with 597 additions and 20 deletions

View File

@@ -1,4 +1,4 @@
<!--
<!--
# Table of Contents
This file contains a single consolidated template for all visual clients.

View File

@@ -0,0 +1,102 @@
import { FormBuilder } from "@angular/forms";
import { mock } from "jest-mock-extended";
import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common";
import { ClientType } from "@bitwarden/common/enums";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { LoginComponent } from "./login.component";
describe("LoginComponent continue() integration", () => {
function createComponent({ flagEnabled }: { flagEnabled: boolean }) {
const activatedRoute: any = { queryParams: { subscribe: () => {} } };
const anonLayoutWrapperDataService: any = { setAnonLayoutWrapperData: () => {} };
const appIdService: any = {};
const broadcasterService: any = { subscribe: () => {}, unsubscribe: () => {} };
const destroyRef: any = {};
const devicesApiService: any = {};
const formBuilder = new FormBuilder();
const i18nService: any = { t: () => "" };
const loginEmailService: any = {
rememberedEmail$: { pipe: () => ({}) },
setLoginEmail: async () => {},
setRememberedEmailChoice: async () => {},
clearLoginEmail: async () => {},
};
const loginComponentService: any = {
showBackButton: () => {},
isLoginWithPasskeySupported: () => false,
redirectToSsoLogin: async () => {},
};
const loginStrategyService = mock<LoginStrategyServiceAbstraction>();
const messagingService: any = { send: () => {} };
const ngZone: any = { isStable: true, onStable: { pipe: () => ({ subscribe: () => {} }) } };
const passwordStrengthService: any = {};
const platformUtilsService = mock<PlatformUtilsService>();
platformUtilsService.getClientType.mockReturnValue(ClientType.Browser);
const policyService: any = { replace: async () => {}, evaluateMasterPassword: () => true };
const router: any = { navigate: async () => {}, navigateByUrl: async () => {} };
const toastService: any = { showToast: () => {} };
const logService: any = { error: () => {} };
const validationService: any = { showError: () => {} };
const loginSuccessHandlerService: any = { run: async () => {} };
const configService = mock<ConfigService>();
configService.getFeatureFlag.mockResolvedValue(flagEnabled);
const ssoLoginService: any = { ssoRequiredCache$: { pipe: () => ({}) } };
const environmentService: any = { environment$: { pipe: () => ({}) } };
const component = new LoginComponent(
activatedRoute,
anonLayoutWrapperDataService,
appIdService,
broadcasterService,
destroyRef,
devicesApiService,
formBuilder,
i18nService,
loginEmailService,
loginComponentService,
loginStrategyService,
messagingService,
ngZone,
passwordStrengthService,
platformUtilsService,
policyService,
router,
toastService,
logService,
validationService,
loginSuccessHandlerService,
configService,
ssoLoginService,
environmentService,
);
jest.spyOn(component as any, "toggleLoginUiState").mockResolvedValue(undefined);
return { component, loginStrategyService };
}
it("calls getPasswordPrelogin on continue when flag enabled and email valid", async () => {
const { component, loginStrategyService } = createComponent({ flagEnabled: true });
(component as any).formGroup.controls.email.setValue("user@example.com");
(component as any).formGroup.controls.rememberEmail.setValue(false);
(component as any).formGroup.controls.masterPassword.setValue("irrelevant");
await (component as any).continue();
expect(loginStrategyService.getPasswordPrelogin).toHaveBeenCalledWith("user@example.com");
});
it("does not call getPasswordPrelogin when flag disabled", async () => {
const { component, loginStrategyService } = createComponent({ flagEnabled: false });
(component as any).formGroup.controls.email.setValue("user@example.com");
(component as any).formGroup.controls.rememberEmail.setValue(false);
(component as any).formGroup.controls.masterPassword.setValue("irrelevant");
await (component as any).continue();
expect(loginStrategyService.getPasswordPrelogin).not.toHaveBeenCalled();
});
});

View File

@@ -550,6 +550,8 @@ export class LoginComponent implements OnInit, OnDestroy {
const isEmailValid = this.validateEmail();
if (isEmailValid) {
await this.makePasswordPreloginCall();
await this.toggleLoginUiState(LoginUiState.MASTER_PASSWORD_ENTRY);
}
}
@@ -652,6 +654,23 @@ export class LoginComponent implements OnInit, OnDestroy {
history.back();
}
private async makePasswordPreloginCall() {
// Prefetch prelogin KDF config when enabled
try {
const flagEnabled = await this.configService.getFeatureFlag(
FeatureFlag.PM23801_PrefetchPasswordPrelogin,
);
if (flagEnabled) {
const email = this.formGroup.value.email;
if (email) {
void this.loginStrategyService.getPasswordPrelogin(email);
}
}
} catch (error) {
this.logService.error("Failed to prefetch prelogin data.", error);
}
}
/**
* Handle the popstate event to transition back to the email entry state when the back button is clicked.
* Also handles the case where the user clicks the forward button.