1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-12-22 16:29:09 +01:00

[PM-10993] Browser Refresh - Fix duplicate password generation emissions in Firefox (#10704)

* [PM-10993] Avoid saving the generation options to state when toggling the password type

* [PM-10993] Fix tests
This commit is contained in:
Shane Melton 2024-08-26 12:05:22 -07:00 committed by GitHub
parent 9e093f88af
commit fbf9c5abfa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 51 additions and 20 deletions

View File

@ -126,15 +126,22 @@ describe("CipherFormGeneratorComponent", () => {
expect(fixture.nativeElement.querySelector("bit-toggle-group")).toBeTruthy(); expect(fixture.nativeElement.querySelector("bit-toggle-group")).toBeTruthy();
}); });
it("should save password options when the password type is updated", async () => { it("should update the generated value when the password type is updated", fakeAsync(async () => {
mockLegacyPasswordGenerationService.generatePassword.mockResolvedValue("generated-password"); mockLegacyPasswordGenerationService.generatePassword
.mockResolvedValueOnce("first-password")
.mockResolvedValueOnce("second-password");
component.ngOnChanges();
tick();
expect(component["generatedValue"]).toBe("first-password");
await component["updatePasswordType"]("passphrase"); await component["updatePasswordType"]("passphrase");
tick();
expect(mockLegacyPasswordGenerationService.saveOptions).toHaveBeenCalledWith({ expect(component["generatedValue"]).toBe("second-password");
type: "passphrase", expect(mockLegacyPasswordGenerationService.generatePassword).toHaveBeenCalledTimes(2);
}); }));
});
it("should update the password history when a new password is generated", fakeAsync(() => { it("should update the password history when a new password is generated", fakeAsync(() => {
mockLegacyPasswordGenerationService.generatePassword.mockResolvedValue("new-password"); mockLegacyPasswordGenerationService.generatePassword.mockResolvedValue("new-password");

View File

@ -1,7 +1,18 @@
import { CommonModule } from "@angular/common"; import { CommonModule } from "@angular/common";
import { Component, DestroyRef, EventEmitter, Input, OnChanges, Output } from "@angular/core"; import { Component, DestroyRef, EventEmitter, Input, OnChanges, Output } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { firstValueFrom, map, startWith, Subject, Subscription, switchMap, tap } from "rxjs"; import {
combineLatest,
map,
merge,
shareReplay,
startWith,
Subject,
Subscription,
switchMap,
take,
tap,
} from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module"; import { JslibModule } from "@bitwarden/angular/jslib.module";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@ -61,18 +72,13 @@ export class CipherFormGeneratorComponent implements OnChanges {
protected regenerateButtonTitle: string; protected regenerateButtonTitle: string;
protected regenerate$ = new Subject<void>(); protected regenerate$ = new Subject<void>();
protected passwordTypeSubject$ = new Subject<GeneratorType>();
/** /**
* The currently generated value displayed to the user. * The currently generated value displayed to the user.
* @protected * @protected
*/ */
protected generatedValue: string = ""; protected generatedValue: string = "";
/**
* The current password generation options.
* @private
*/
private passwordOptions$ = this.legacyPasswordGenerationService.getOptions$();
/** /**
* The current username generation options. * The current username generation options.
* @private * @private
@ -80,10 +86,30 @@ export class CipherFormGeneratorComponent implements OnChanges {
private usernameOptions$ = this.legacyUsernameGenerationService.getOptions$(); private usernameOptions$ = this.legacyUsernameGenerationService.getOptions$();
/** /**
* The current password type specified by the password generation options. * The current password type selected in the UI. Starts with the saved value from the service.
* @protected * @protected
*/ */
protected passwordType$ = this.passwordOptions$.pipe(map(([options]) => options.type)); protected passwordType$ = merge(
this.legacyPasswordGenerationService.getOptions$().pipe(
take(1),
map(([options]) => options.type),
),
this.passwordTypeSubject$,
).pipe(shareReplay({ bufferSize: 1, refCount: false }));
/**
* The current password generation options.
* @private
*/
private passwordOptions$ = combineLatest([
this.legacyPasswordGenerationService.getOptions$(),
this.passwordType$,
]).pipe(
map(([[options], type]) => {
options.type = type;
return options;
}),
);
/** /**
* Tracks the regenerate$ subscription * Tracks the regenerate$ subscription
@ -121,7 +147,7 @@ export class CipherFormGeneratorComponent implements OnChanges {
.pipe( .pipe(
startWith(null), startWith(null),
switchMap(() => this.passwordOptions$), switchMap(() => this.passwordOptions$),
switchMap(([options]) => this.legacyPasswordGenerationService.generatePassword(options)), switchMap((options) => this.legacyPasswordGenerationService.generatePassword(options)),
tap(async (password) => { tap(async (password) => {
await this.legacyPasswordGenerationService.addHistory(password); await this.legacyPasswordGenerationService.addHistory(password);
}), }),
@ -148,12 +174,10 @@ export class CipherFormGeneratorComponent implements OnChanges {
} }
/** /**
* Switch the password generation type and save the options (generating a new password automatically). * Switch the password generation type.
* @param value The new password generation type. * @param value The new password generation type.
*/ */
protected updatePasswordType = async (value: GeneratorType) => { protected updatePasswordType = async (value: GeneratorType) => {
const [currentOptions] = await firstValueFrom(this.passwordOptions$); this.passwordTypeSubject$.next(value);
currentOptions.type = value;
await this.legacyPasswordGenerationService.saveOptions(currentOptions);
}; };
} }