1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-10 13:40:06 +00:00

feat(extension-device-approval): [PM-14943] Answering Service Full Implementation - Can respond to requests that came in from the background now.

This commit is contained in:
Patrick Pimentel
2025-08-22 15:49:14 -04:00
parent 7aadd36c6d
commit 854a2333c2
9 changed files with 230 additions and 19 deletions

View File

@@ -41,6 +41,9 @@ import { UserVerificationService as UserVerificationServiceAbstraction } from "@
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service";
import { AuthRequestAnsweringService } from "@bitwarden/common/auth/services/auth-request-answering/auth-request-answering.service";
import {
PendingAuthRequestsStateService
} from "@bitwarden/common/auth/services/auth-request-answering/pending-auth-requests.state";
import { AuthService } from "@bitwarden/common/auth/services/auth.service";
import { AvatarService } from "@bitwarden/common/auth/services/avatar.service";
import { DefaultActiveUserAccessor } from "@bitwarden/common/auth/services/default-active-user.accessor";
@@ -401,6 +404,7 @@ export default class MainBackground {
individualVaultExportService: IndividualVaultExportServiceAbstraction;
organizationVaultExportService: OrganizationVaultExportServiceAbstraction;
vaultSettingsService: VaultSettingsServiceAbstraction;
pendingAuthRequestStateService: PendingAuthRequestsStateService;
biometricStateService: BiometricStateService;
biometricsService: BiometricsService;
stateEventRunnerService: StateEventRunnerService;
@@ -1129,12 +1133,16 @@ export default class MainBackground {
this.systemNotificationService = new UnsupportedSystemNotificationsService();
}
this.pendingAuthRequestStateService = new PendingAuthRequestsStateService(this.stateProvider);
this.authRequestAnsweringService = new AuthRequestAnsweringService(
this.accountService,
this.actionsService,
this.authService,
this.i18nService,
this.masterPasswordService,
this.messagingService,
this.pendingAuthRequestStateService,
this.platformUtilsService,
this.systemNotificationService,
);

View File

@@ -23,13 +23,20 @@ import {
map,
} from "rxjs";
import { LoginApprovalDialogComponent } from "@bitwarden/angular/auth/login-approval/login-approval-dialog.component";
import { DeviceTrustToastService } from "@bitwarden/angular/auth/services/device-trust-toast.service.abstraction";
import { DocumentLangSetter } from "@bitwarden/angular/platform/i18n";
import { LogoutReason, UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
import {
LogoutReason,
AuthRequestServiceAbstraction,
UserDecryptionOptionsServiceAbstraction,
} from "@bitwarden/auth/common";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AuthRequestAnsweringServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth-request-answering/auth-request-answering.service.abstraction";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { PendingAuthRequestsStateService } from "@bitwarden/common/auth/services/auth-request-answering/pending-auth-requests.state";
import { AnimationControlService } from "@bitwarden/common/platform/abstractions/animation-control.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -122,6 +129,9 @@ export class AppComponent implements OnInit, OnDestroy {
private readonly documentLangSetter: DocumentLangSetter,
private popupSizeService: PopupSizeService,
private logService: LogService,
private authRequestService: AuthRequestServiceAbstraction,
private pendingAuthRequestsState: PendingAuthRequestsStateService,
private authRequestAnsweringService: AuthRequestAnsweringServiceAbstraction,
) {
this.deviceTrustToastService.setupListeners$.pipe(takeUntilDestroyed()).subscribe();
@@ -141,6 +151,8 @@ export class AppComponent implements OnInit, OnDestroy {
this.accountService.activeAccount$.pipe(takeUntil(this.destroy$)).subscribe((account) => {
this.activeUserId = account?.id;
// Re-evaluate pending auth requests when switching users while popup is open
void this.authRequestAnsweringService.processPendingAuthRequests();
});
this.authService.activeAccountStatus$
@@ -206,6 +218,43 @@ export class AppComponent implements OnInit, OnDestroy {
}
await this.router.navigate(["lock"]);
} else if (msg.command === "openLoginApproval") {
// Always query server for all pending requests and open a dialog for each
const pendingList = await firstValueFrom(this.authRequestService.getPendingAuthRequests$());
if (Array.isArray(pendingList) && pendingList.length > 0) {
const respondedIds = new Set<string>();
for (const req of pendingList) {
if (req?.id == null) {continue;}
console.debug(
"[Popup AppComponent] Opening LoginApprovalDialogComponent",
req.id,
);
const dialogRef = LoginApprovalDialogComponent.open(this.dialogService, {
notificationId: req.id,
});
const result = await firstValueFrom(dialogRef.closed);
if (result !== undefined && typeof result === "boolean") {
respondedIds.add(req.id);
if (
respondedIds.size === pendingList.length &&
this.activeUserId != null
) {
console.debug(
"[Popup AppComponent] All pending auth requests responded; clearing marker for user",
this.activeUserId,
);
await this.pendingAuthRequestsState.clearByUserId(this.activeUserId);
}
}
console.debug(
"[Popup AppComponent] LoginApprovalDialogComponent closed",
req.id,
);
}
}
} else if (msg.command === "showDialog") {
// 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
@@ -321,6 +370,7 @@ export class AppComponent implements OnInit, OnDestroy {
}
private async clearComponentStates() {
if (this.activeUserId == null) {return;}
if (!(await firstValueFrom(this.tokenService.hasAccessToken$(this.activeUserId)))) {
return;
}