1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-21 10:43:35 +00:00

[PM-14419] At-risk passwords change password service (#13279)

* [PM-14419] Introduce the change-login-password service and its default implementation

* [PM-14419] Use the change login password service on the at-risk passwords page

* [PM-14419] Add unit tests

* [PM-14419] Use existing fixed test environment

* [PM-14419] Add mock implementation for ChangeLoginPasswordService in at-risk passwords tests

* [PM-14419] Linter
This commit is contained in:
Shane Melton
2025-02-13 10:58:44 -08:00
committed by GitHub
parent a0c38543ac
commit c67e6df839
7 changed files with 319 additions and 15 deletions

View File

@@ -59,11 +59,20 @@
type="button"
bitBadge
variant="primary"
appStopProp
(click)="launchChangePassword(cipher)"
[title]="'changeButtonTitle' | i18n: cipher.name"
[attr.aria-label]="'changeButtonTitle' | i18n: cipher.name"
[disabled]="launchingCipher() == cipher"
>
{{ "change" | i18n }}
<ng-container *ngIf="launchingCipher() != cipher">
{{ "change" | i18n }}
</ng-container>
<i
*ngIf="launchingCipher() == cipher"
class="bwi bwi-spinner bwi-spin"
aria-hidden="true"
></i>
</button>
</bit-item-action>
</bit-item>

View File

@@ -18,6 +18,8 @@ import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.servi
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { ToastService } from "@bitwarden/components";
import {
ChangeLoginPasswordService,
DefaultChangeLoginPasswordService,
PasswordRepromptService,
SecurityTask,
SecurityTaskType,
@@ -70,6 +72,7 @@ describe("AtRiskPasswordsComponent", () => {
const setInlineMenuVisibility = jest.fn();
const mockToastService = mock<ToastService>();
const mockAtRiskPasswordPageService = mock<AtRiskPasswordPageService>();
const mockChangeLoginPasswordService = mock<ChangeLoginPasswordService>();
beforeEach(async () => {
mockTasks$ = new BehaviorSubject<SecurityTask[]>([
@@ -156,12 +159,16 @@ describe("AtRiskPasswordsComponent", () => {
.overrideComponent(AtRiskPasswordsComponent, {
remove: {
imports: [PopupHeaderComponent, PopupPageComponent],
providers: [AtRiskPasswordPageService],
providers: [
AtRiskPasswordPageService,
{ provide: ChangeLoginPasswordService, useClass: DefaultChangeLoginPasswordService },
],
},
add: {
imports: [MockPopupHeaderComponent, MockPopupPageComponent],
providers: [
{ provide: AtRiskPasswordPageService, useValue: mockAtRiskPasswordPageService },
{ provide: ChangeLoginPasswordService, useValue: mockChangeLoginPasswordService },
],
},
})

View File

@@ -1,5 +1,5 @@
import { CommonModule } from "@angular/common";
import { Component, inject } from "@angular/core";
import { Component, inject, signal } from "@angular/core";
import { Router } from "@angular/router";
import { combineLatest, firstValueFrom, map, of, shareReplay, startWith, switchMap } from "rxjs";
@@ -16,7 +16,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import {
BadgeComponent,
BadgeModule,
ButtonModule,
CalloutModule,
ItemModule,
@@ -24,6 +24,8 @@ import {
TypographyModule,
} from "@bitwarden/components";
import {
ChangeLoginPasswordService,
DefaultChangeLoginPasswordService,
filterOutNullish,
PasswordRepromptService,
SecurityTaskType,
@@ -37,8 +39,6 @@ import { PopupPageComponent } from "../../../../platform/popup/layout/popup-page
import { AtRiskPasswordPageService } from "./at-risk-password-page.service";
@Component({
selector: "vault-at-risk-passwords",
standalone: true,
imports: [
PopupPageComponent,
PopupHeaderComponent,
@@ -46,12 +46,17 @@ import { AtRiskPasswordPageService } from "./at-risk-password-page.service";
ItemModule,
CommonModule,
JslibModule,
BadgeComponent,
TypographyModule,
CalloutModule,
ButtonModule,
BadgeModule,
],
providers: [AtRiskPasswordPageService],
providers: [
AtRiskPasswordPageService,
{ provide: ChangeLoginPasswordService, useClass: DefaultChangeLoginPasswordService },
],
selector: "vault-at-risk-passwords",
standalone: true,
templateUrl: "./at-risk-passwords.component.html",
})
export class AtRiskPasswordsComponent {
@@ -60,12 +65,20 @@ export class AtRiskPasswordsComponent {
private cipherService = inject(CipherService);
private i18nService = inject(I18nService);
private accountService = inject(AccountService);
private platformUtilsService = inject(PlatformUtilsService);
private passwordRepromptService = inject(PasswordRepromptService);
private router = inject(Router);
private autofillSettingsService = inject(AutofillSettingsServiceAbstraction);
private toastService = inject(ToastService);
private atRiskPasswordPageService = inject(AtRiskPasswordPageService);
private changeLoginPasswordService = inject(ChangeLoginPasswordService);
private platformUtilsService = inject(PlatformUtilsService);
/**
* The cipher that is currently being launched. Used to show a loading spinner on the badge button.
* The UI utilize a bitBadge which does not support async actions (like bitButton does).
* @protected
*/
protected launchingCipher = signal<CipherView | null>(null);
private activeUserData$ = this.accountService.activeAccount$.pipe(
filterOutNullish(),
@@ -138,12 +151,6 @@ export class AtRiskPasswordsComponent {
});
}
async launchChangePassword(cipher: CipherView) {
if (cipher.login?.uri) {
this.platformUtilsService.launchUri(cipher.login.uri);
}
}
async activateInlineAutofillMenuVisibility() {
await this.autofillSettingsService.setInlineMenuVisibility(
AutofillOverlayVisibility.OnButtonClick,
@@ -159,4 +166,19 @@ export class AtRiskPasswordsComponent {
const { userId } = await firstValueFrom(this.activeUserData$);
await this.atRiskPasswordPageService.dismissCallout(userId);
}
launchChangePassword = async (cipher: CipherView) => {
try {
this.launchingCipher.set(cipher);
const url = await this.changeLoginPasswordService.getChangePasswordUrl(cipher);
if (url == null) {
return;
}
this.platformUtilsService.launchUri(url);
} finally {
this.launchingCipher.set(null);
}
};
}