mirror of
https://github.com/bitwarden/browser
synced 2026-02-09 21:20:27 +00:00
separate extension/default services
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AuthRequestAnsweringService } from "@bitwarden/common/auth/abstractions/auth-request-answering/auth-request-answering.service.abstraction";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { AuthServerNotificationTags } from "@bitwarden/common/auth/enums/auth-server-notification-tags";
|
||||
import { DefaultAuthRequestAnsweringService } from "@bitwarden/common/auth/services/auth-request-answering/default-auth-request-answering.service";
|
||||
import { PendingAuthRequestsStateService } from "@bitwarden/common/auth/services/auth-request-answering/pending-auth-requests.state";
|
||||
import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { ActionsService } from "@bitwarden/common/platform/actions";
|
||||
import {
|
||||
ButtonLocation,
|
||||
SystemNotificationEvent,
|
||||
SystemNotificationsService,
|
||||
} from "@bitwarden/common/platform/system-notifications/system-notifications.service";
|
||||
import { UserId } from "@bitwarden/user-core";
|
||||
|
||||
export class ExtensionAuthRequestAnsweringService
|
||||
extends DefaultAuthRequestAnsweringService
|
||||
implements AuthRequestAnsweringService
|
||||
{
|
||||
constructor(
|
||||
protected readonly accountService: AccountService,
|
||||
protected readonly authService: AuthService,
|
||||
protected readonly masterPasswordService: MasterPasswordServiceAbstraction,
|
||||
protected readonly messagingService: MessagingService,
|
||||
protected readonly pendingAuthRequestsState: PendingAuthRequestsStateService,
|
||||
private readonly actionService: ActionsService,
|
||||
private readonly i18nService: I18nService,
|
||||
private readonly platformUtilsService: PlatformUtilsService,
|
||||
private readonly systemNotificationsService: SystemNotificationsService,
|
||||
) {
|
||||
super(
|
||||
accountService,
|
||||
authService,
|
||||
masterPasswordService,
|
||||
messagingService,
|
||||
pendingAuthRequestsState,
|
||||
);
|
||||
}
|
||||
|
||||
override async receivedPendingAuthRequest(userId: UserId, authRequestId?: string): Promise<void> {
|
||||
if (!authRequestId) {
|
||||
throw new Error("authRequestId not found.");
|
||||
}
|
||||
|
||||
// Always persist the pending marker for this user to global state.
|
||||
await this.pendingAuthRequestsState.add(userId);
|
||||
|
||||
const userIsAvailableToViewDialog = await this.userMeetsConditionsToShowApprovalDialog(userId);
|
||||
|
||||
if (userIsAvailableToViewDialog) {
|
||||
// Send message to open dialog immediately for this request
|
||||
this.messagingService.send("openLoginApproval");
|
||||
} else {
|
||||
// Create a system notification
|
||||
const accounts = await firstValueFrom(this.accountService.accounts$);
|
||||
const emailForUser = accounts[userId].email;
|
||||
await this.systemNotificationsService.create({
|
||||
id: `${AuthServerNotificationTags.AuthRequest}_${authRequestId}`, // the underscore is an important delimiter.
|
||||
title: this.i18nService.t("accountAccessRequested"),
|
||||
body: this.i18nService.t("confirmAccessAttempt", emailForUser),
|
||||
buttons: [],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async userMeetsConditionsToShowApprovalDialog(userId: UserId): Promise<boolean> {
|
||||
const meetsBasicConditions = await super.userMeetsConditionsToShowApprovalDialog(userId);
|
||||
|
||||
// To show an approval dialog immediately on Extension, the popup must be open.
|
||||
const isPopupOpen = await this.platformUtilsService.isPopupOpen();
|
||||
const meetsExtensionConditions = meetsBasicConditions && isPopupOpen;
|
||||
|
||||
return meetsExtensionConditions;
|
||||
}
|
||||
|
||||
/**
|
||||
* When a system notification is clicked, this function is used to process that event.
|
||||
*
|
||||
* @param event The event passed in. Check initNotificationSubscriptions in main.background.ts.
|
||||
*/
|
||||
async handleAuthRequestNotificationClicked(event: SystemNotificationEvent): Promise<void> {
|
||||
if (event.buttonIdentifier === ButtonLocation.NotificationButton) {
|
||||
await this.systemNotificationsService.clear({
|
||||
id: `${event.id}`,
|
||||
});
|
||||
await this.actionService.openPopup();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,6 @@ import { DefaultPolicyService } from "@bitwarden/common/admin-console/services/p
|
||||
import { PolicyApiService } from "@bitwarden/common/admin-console/services/policy/policy-api.service";
|
||||
import { ProviderService } from "@bitwarden/common/admin-console/services/provider.service";
|
||||
import { AccountService as AccountServiceAbstraction } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AuthRequestAnsweringServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth-request-answering/auth-request-answering.service.abstraction";
|
||||
import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { AvatarService as AvatarServiceAbstraction } from "@bitwarden/common/auth/abstractions/avatar.service";
|
||||
import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction";
|
||||
@@ -51,7 +50,6 @@ import { UserVerificationService as UserVerificationServiceAbstraction } from "@
|
||||
import { AuthServerNotificationTags } from "@bitwarden/common/auth/enums/auth-server-notification-tags";
|
||||
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";
|
||||
@@ -265,6 +263,7 @@ import {
|
||||
VaultExportServiceAbstraction,
|
||||
} from "@bitwarden/vault-export-core";
|
||||
|
||||
import { ExtensionAuthRequestAnsweringService } from "../auth/services/auth-request-answering/extension-auth-request-answering.service";
|
||||
import { AuthStatusBadgeUpdaterService } from "../auth/services/auth-status-badge-updater.service";
|
||||
import { OverlayNotificationsBackground as OverlayNotificationsBackgroundInterface } from "../autofill/background/abstractions/overlay-notifications.background";
|
||||
import { OverlayBackground as OverlayBackgroundInterface } from "../autofill/background/abstractions/overlay.background";
|
||||
@@ -375,7 +374,7 @@ export default class MainBackground {
|
||||
serverNotificationsService: ServerNotificationsService;
|
||||
systemNotificationService: SystemNotificationsService;
|
||||
actionsService: ActionsService;
|
||||
authRequestAnsweringService: AuthRequestAnsweringServiceAbstraction;
|
||||
authRequestAnsweringService: ExtensionAuthRequestAnsweringService;
|
||||
stateService: StateServiceAbstraction;
|
||||
userNotificationSettingsService: UserNotificationSettingsServiceAbstraction;
|
||||
autofillSettingsService: AutofillSettingsServiceAbstraction;
|
||||
@@ -1173,14 +1172,14 @@ export default class MainBackground {
|
||||
|
||||
this.pendingAuthRequestStateService = new PendingAuthRequestsStateService(this.stateProvider);
|
||||
|
||||
this.authRequestAnsweringService = new AuthRequestAnsweringService(
|
||||
this.authRequestAnsweringService = new ExtensionAuthRequestAnsweringService(
|
||||
this.accountService,
|
||||
this.actionsService,
|
||||
this.authService,
|
||||
this.i18nService,
|
||||
this.masterPasswordService,
|
||||
this.messagingService,
|
||||
this.pendingAuthRequestStateService,
|
||||
this.actionsService,
|
||||
this.i18nService,
|
||||
this.platformUtilsService,
|
||||
this.systemNotificationService,
|
||||
);
|
||||
|
||||
@@ -37,7 +37,7 @@ import {
|
||||
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 { AuthRequestAnsweringService } 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";
|
||||
@@ -114,7 +114,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
private logService: LogService,
|
||||
private authRequestService: AuthRequestServiceAbstraction,
|
||||
private pendingAuthRequestsState: PendingAuthRequestsStateService,
|
||||
private authRequestAnsweringService: AuthRequestAnsweringServiceAbstraction,
|
||||
private authRequestAnsweringService: AuthRequestAnsweringService,
|
||||
) {
|
||||
this.deviceTrustToastService.setupListeners$.pipe(takeUntilDestroyed()).subscribe();
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
SsoUrlService,
|
||||
LogoutService,
|
||||
} from "@bitwarden/auth/common";
|
||||
import { ExtensionAuthRequestAnsweringService } from "@bitwarden/browser/auth/services/auth-request-answering/extension-auth-request-answering.service";
|
||||
import { ExtensionNewDeviceVerificationComponentService } from "@bitwarden/browser/auth/services/new-device-verification/extension-new-device-verification-component.service";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service";
|
||||
@@ -45,13 +46,12 @@ import {
|
||||
AccountService,
|
||||
AccountService as AccountServiceAbstraction,
|
||||
} from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AuthRequestAnsweringServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth-request-answering/auth-request-answering.service.abstraction";
|
||||
import { AuthRequestAnsweringService } from "@bitwarden/common/auth/abstractions/auth-request-answering/auth-request-answering.service.abstraction";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
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 {
|
||||
AutofillSettingsService,
|
||||
@@ -484,16 +484,16 @@ const safeProviders: SafeProvider[] = [
|
||||
deps: [],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: AuthRequestAnsweringServiceAbstraction,
|
||||
useClass: AuthRequestAnsweringService,
|
||||
provide: AuthRequestAnsweringService,
|
||||
useClass: ExtensionAuthRequestAnsweringService,
|
||||
deps: [
|
||||
AccountServiceAbstraction,
|
||||
ActionsService,
|
||||
AuthService,
|
||||
I18nServiceAbstraction,
|
||||
MasterPasswordServiceAbstraction,
|
||||
MessagingService,
|
||||
PendingAuthRequestsStateService,
|
||||
ActionsService,
|
||||
I18nServiceAbstraction,
|
||||
PlatformUtilsService,
|
||||
SystemNotificationsService,
|
||||
],
|
||||
|
||||
@@ -31,6 +31,7 @@ import { DocumentLangSetter } from "@bitwarden/angular/platform/i18n";
|
||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||
import { FingerprintDialogComponent } from "@bitwarden/auth/angular";
|
||||
import {
|
||||
AuthRequestServiceAbstraction,
|
||||
DESKTOP_SSO_CALLBACK,
|
||||
LogoutReason,
|
||||
UserDecryptionOptionsServiceAbstraction,
|
||||
@@ -44,6 +45,7 @@ import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"
|
||||
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { PendingAuthRequestsStateService } from "@bitwarden/common/auth/services/auth-request-answering/pending-auth-requests.state";
|
||||
import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service";
|
||||
import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service";
|
||||
import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||
@@ -133,6 +135,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
private isIdle = false;
|
||||
private activeUserId: UserId = null;
|
||||
private activeSimpleDialog: DialogRef<boolean> = null;
|
||||
private processingPendingAuth = false;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
@@ -179,6 +182,8 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
private restrictedItemTypesService: RestrictedItemTypesService,
|
||||
private readonly tokenService: TokenService,
|
||||
private desktopAutotypeDefaultSettingPolicy: DesktopAutotypeDefaultSettingPolicy,
|
||||
private pendingAuthRequestsState: PendingAuthRequestsStateService,
|
||||
private authRequestService: AuthRequestServiceAbstraction,
|
||||
) {
|
||||
this.deviceTrustToastService.setupListeners$.pipe(takeUntilDestroyed()).subscribe();
|
||||
|
||||
@@ -489,13 +494,49 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
await this.checkForSystemTimeout(VaultTimeoutStringType.OnIdle);
|
||||
break;
|
||||
case "openLoginApproval":
|
||||
if (message.notificationId != null) {
|
||||
this.dialogService.closeAll();
|
||||
const dialogRef = LoginApprovalDialogComponent.open(this.dialogService, {
|
||||
notificationId: message.notificationId,
|
||||
});
|
||||
await firstValueFrom(dialogRef.closed);
|
||||
if (this.processingPendingAuth) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.processingPendingAuth = true;
|
||||
|
||||
try {
|
||||
// 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;
|
||||
}
|
||||
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) {
|
||||
await this.pendingAuthRequestsState.clear(this.activeUserId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
this.processingPendingAuth = false;
|
||||
}
|
||||
|
||||
// if (message.notificationId != null) {
|
||||
// this.dialogService.closeAll();
|
||||
// const dialogRef = LoginApprovalDialogComponent.open(this.dialogService, {
|
||||
// notificationId: message.notificationId,
|
||||
// });
|
||||
// await firstValueFrom(dialogRef.closed);
|
||||
// }
|
||||
|
||||
break;
|
||||
case "redrawMenu":
|
||||
await this.updateAppMenu();
|
||||
|
||||
@@ -51,9 +51,11 @@ import {
|
||||
} from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { AccountApiService as AccountApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/account-api.service";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AuthRequestAnsweringService } from "@bitwarden/common/auth/abstractions/auth-request-answering/auth-request-answering.service.abstraction";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { MasterPasswordApiService } from "@bitwarden/common/auth/abstractions/master-password-api.service.abstraction";
|
||||
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
|
||||
import { NoopAuthRequestAnsweringService } from "@bitwarden/common/auth/services/auth-request-answering/noop-auth-request-answering.service";
|
||||
import { OrganizationInviteService } from "@bitwarden/common/auth/services/organization-invite/organization-invite.service";
|
||||
import { ClientType } from "@bitwarden/common/enums";
|
||||
import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service";
|
||||
@@ -410,6 +412,11 @@ const safeProviders: SafeProvider[] = [
|
||||
useClass: WebVaultPremiumUpgradePromptService,
|
||||
deps: [DialogService, Router],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: AuthRequestAnsweringService,
|
||||
useClass: NoopAuthRequestAnsweringService,
|
||||
deps: [],
|
||||
}),
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
||||
@@ -89,7 +89,7 @@ import {
|
||||
InternalAccountService,
|
||||
} from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AnonymousHubService as AnonymousHubServiceAbstraction } from "@bitwarden/common/auth/abstractions/anonymous-hub.service";
|
||||
import { AuthRequestAnsweringServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth-request-answering/auth-request-answering.service.abstraction";
|
||||
import { AuthRequestAnsweringService } from "@bitwarden/common/auth/abstractions/auth-request-answering/auth-request-answering.service.abstraction";
|
||||
import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { AvatarService as AvatarServiceAbstraction } from "@bitwarden/common/auth/abstractions/avatar.service";
|
||||
import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction";
|
||||
@@ -108,7 +108,7 @@ import { SendTokenService, DefaultSendTokenService } from "@bitwarden/common/aut
|
||||
import { AccountApiServiceImplementation } from "@bitwarden/common/auth/services/account-api.service";
|
||||
import { AccountServiceImplementation } from "@bitwarden/common/auth/services/account.service";
|
||||
import { AnonymousHubService } from "@bitwarden/common/auth/services/anonymous-hub.service";
|
||||
import { NoopAuthRequestAnsweringService } from "@bitwarden/common/auth/services/auth-request-answering/noop-auth-request-answering.service";
|
||||
import { DefaultAuthRequestAnsweringService } from "@bitwarden/common/auth/services/auth-request-answering/default-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";
|
||||
@@ -992,9 +992,15 @@ const safeProviders: SafeProvider[] = [
|
||||
deps: [StateProvider],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: AuthRequestAnsweringServiceAbstraction,
|
||||
useClass: NoopAuthRequestAnsweringService,
|
||||
deps: [],
|
||||
provide: AuthRequestAnsweringService,
|
||||
useClass: DefaultAuthRequestAnsweringService,
|
||||
deps: [
|
||||
AccountServiceAbstraction,
|
||||
AuthServiceAbstraction,
|
||||
MasterPasswordServiceAbstraction,
|
||||
MessagingServiceAbstraction,
|
||||
PendingAuthRequestsStateService,
|
||||
],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: ServerNotificationsService,
|
||||
@@ -1012,7 +1018,7 @@ const safeProviders: SafeProvider[] = [
|
||||
SignalRConnectionService,
|
||||
AuthServiceAbstraction,
|
||||
WebPushConnectionService,
|
||||
AuthRequestAnsweringServiceAbstraction,
|
||||
AuthRequestAnsweringService,
|
||||
ConfigService,
|
||||
],
|
||||
}),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# Auth Request Answering Service
|
||||
|
||||
This feature is to allow for the taking of auth requests that are received via websockets by the background service to
|
||||
be acted on when the user loads up a client. Currently only implemented with the browser client.
|
||||
This feature is to allow for the taking of auth requests that are received via websockets to be acted on when the user loads up a client.
|
||||
|
||||
See diagram for the high level picture of how this is wired up.
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { SystemNotificationEvent } from "@bitwarden/common/platform/system-notifications/system-notifications.service";
|
||||
import { UserId } from "@bitwarden/user-core";
|
||||
|
||||
export abstract class AuthRequestAnsweringServiceAbstraction {
|
||||
export abstract class AuthRequestAnsweringService {
|
||||
/**
|
||||
* Tries to either display the dialog for the user or will preserve its data and show it at a
|
||||
* later time. Even in the event the dialog is shown immediately, this will write to global state
|
||||
@@ -13,14 +12,20 @@ export abstract class AuthRequestAnsweringServiceAbstraction {
|
||||
* @param userId The UserId that the auth request is for.
|
||||
* @param authRequestId The id of the auth request that is to be processed.
|
||||
*/
|
||||
abstract receivedPendingAuthRequest(userId: UserId, authRequestId: string): Promise<void>;
|
||||
abstract receivedPendingAuthRequest(userId: UserId, authRequestId?: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* When a system notification is clicked, this function is used to process that event.
|
||||
* Confirms whether or not the user meets the conditions required to show an approval
|
||||
* dialog immediately. Those conditions are:
|
||||
* - User must be Unlocked
|
||||
* - User must be the active user
|
||||
* - User must not be required to set/change their password
|
||||
*
|
||||
* @param event The event passed in. Check initNotificationSubscriptions in main.background.ts.
|
||||
* @param userId the UserId that the auth request is for.
|
||||
* @returns boolean stating whether or not the user meets conditions necessary to show
|
||||
* an approval dialog immediately.
|
||||
*/
|
||||
abstract handleAuthRequestNotificationClicked(event: SystemNotificationEvent): Promise<void>;
|
||||
abstract userMeetsConditionsToShowApprovalDialog(userId: UserId): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Process notifications that have been received but didn't meet the conditions to display the
|
||||
|
||||
@@ -17,7 +17,8 @@ import {
|
||||
} from "@bitwarden/common/platform/system-notifications/system-notifications.service";
|
||||
import { UserId } from "@bitwarden/user-core";
|
||||
|
||||
import { AuthRequestAnsweringService } from "./auth-request-answering.service";
|
||||
import { AuthRequestAnsweringService } from "../../abstractions/auth-request-answering/auth-request-answering.service.abstraction";
|
||||
|
||||
import { PendingAuthRequestsStateService } from "./pending-auth-requests.state";
|
||||
|
||||
describe("AuthRequestAnsweringService", () => {
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { AuthServerNotificationTags } from "@bitwarden/common/auth/enums/auth-server-notification-tags";
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
|
||||
import { getOptionalUserId, getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { ActionsService } from "@bitwarden/common/platform/actions";
|
||||
import {
|
||||
ButtonLocation,
|
||||
SystemNotificationEvent,
|
||||
SystemNotificationsService,
|
||||
} from "@bitwarden/common/platform/system-notifications/system-notifications.service";
|
||||
import { UserId } from "@bitwarden/user-core";
|
||||
|
||||
import { AuthRequestAnsweringServiceAbstraction } from "../../abstractions/auth-request-answering/auth-request-answering.service.abstraction";
|
||||
|
||||
import {
|
||||
PendingAuthRequestsStateService,
|
||||
PendingAuthUserMarker,
|
||||
} from "./pending-auth-requests.state";
|
||||
|
||||
export class AuthRequestAnsweringService implements AuthRequestAnsweringServiceAbstraction {
|
||||
constructor(
|
||||
private readonly accountService: AccountService,
|
||||
private readonly actionService: ActionsService,
|
||||
private readonly authService: AuthService,
|
||||
private readonly i18nService: I18nService,
|
||||
private readonly masterPasswordService: MasterPasswordServiceAbstraction,
|
||||
private readonly messagingService: MessagingService,
|
||||
private readonly pendingAuthRequestsState: PendingAuthRequestsStateService,
|
||||
private readonly platformUtilsService: PlatformUtilsService,
|
||||
private readonly systemNotificationsService: SystemNotificationsService,
|
||||
) {}
|
||||
|
||||
async receivedPendingAuthRequest(userId: UserId, authRequestId: string): Promise<void> {
|
||||
const authStatus = await firstValueFrom(this.authService.activeAccountStatus$);
|
||||
const activeUserId: UserId | null = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(getOptionalUserId),
|
||||
);
|
||||
const forceSetPasswordReason = await firstValueFrom(
|
||||
this.masterPasswordService.forceSetPasswordReason$(userId),
|
||||
);
|
||||
const popupOpen = await this.platformUtilsService.isPopupOpen();
|
||||
|
||||
// Always persist the pending marker for this user to global state.
|
||||
await this.pendingAuthRequestsState.add(userId);
|
||||
|
||||
// These are the conditions we are looking for to know if the extension is in a state to show
|
||||
// the approval dialog.
|
||||
const userIsAvailableToReceiveAuthRequest =
|
||||
popupOpen &&
|
||||
authStatus === AuthenticationStatus.Unlocked &&
|
||||
activeUserId === userId &&
|
||||
forceSetPasswordReason === ForceSetPasswordReason.None;
|
||||
|
||||
if (!userIsAvailableToReceiveAuthRequest) {
|
||||
// Get the user's email to include in the system notification
|
||||
const accounts = await firstValueFrom(this.accountService.accounts$);
|
||||
const emailForUser = accounts[userId].email;
|
||||
|
||||
await this.systemNotificationsService.create({
|
||||
id: `${AuthServerNotificationTags.AuthRequest}_${authRequestId}`, // the underscore is an important delimiter.
|
||||
title: this.i18nService.t("accountAccessRequested"),
|
||||
body: this.i18nService.t("confirmAccessAttempt", emailForUser),
|
||||
buttons: [],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Popup is open and conditions are met; open dialog immediately for this request
|
||||
this.messagingService.send("openLoginApproval");
|
||||
}
|
||||
|
||||
async handleAuthRequestNotificationClicked(event: SystemNotificationEvent): Promise<void> {
|
||||
if (event.buttonIdentifier === ButtonLocation.NotificationButton) {
|
||||
await this.systemNotificationsService.clear({
|
||||
id: `${event.id}`,
|
||||
});
|
||||
await this.actionService.openPopup();
|
||||
}
|
||||
}
|
||||
|
||||
async processPendingAuthRequests(): Promise<void> {
|
||||
// Prune any stale pending requests (older than 15 minutes)
|
||||
// This comes from GlobalSettings.cs
|
||||
// public TimeSpan UserRequestExpiration { get; set; } = TimeSpan.FromMinutes(15);
|
||||
const fifteenMinutesMs = 15 * 60 * 1000;
|
||||
|
||||
await this.pendingAuthRequestsState.pruneOlderThan(fifteenMinutesMs);
|
||||
|
||||
const pendingAuthRequestsInState: PendingAuthUserMarker[] =
|
||||
(await firstValueFrom(this.pendingAuthRequestsState.getAll$())) ?? [];
|
||||
|
||||
if (pendingAuthRequestsInState.length > 0) {
|
||||
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
const pendingAuthRequestsForActiveUser = pendingAuthRequestsInState.some(
|
||||
(e) => e.userId === activeUserId,
|
||||
);
|
||||
|
||||
if (pendingAuthRequestsForActiveUser) {
|
||||
this.messagingService.send("openLoginApproval");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
|
||||
import { getOptionalUserId, getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
|
||||
import { UserId } from "@bitwarden/user-core";
|
||||
|
||||
import { AuthRequestAnsweringService } from "../../abstractions/auth-request-answering/auth-request-answering.service.abstraction";
|
||||
|
||||
import {
|
||||
PendingAuthRequestsStateService,
|
||||
PendingAuthUserMarker,
|
||||
} from "./pending-auth-requests.state";
|
||||
|
||||
export class DefaultAuthRequestAnsweringService implements AuthRequestAnsweringService {
|
||||
constructor(
|
||||
protected readonly accountService: AccountService,
|
||||
protected readonly authService: AuthService,
|
||||
protected readonly masterPasswordService: MasterPasswordServiceAbstraction,
|
||||
protected readonly messagingService: MessagingService,
|
||||
protected readonly pendingAuthRequestsState: PendingAuthRequestsStateService,
|
||||
) {}
|
||||
|
||||
async receivedPendingAuthRequest(userId: UserId): Promise<void> {
|
||||
// Always persist the pending marker for this user to global state.
|
||||
await this.pendingAuthRequestsState.add(userId);
|
||||
|
||||
const userIsAvailableToViewDialog = await this.userMeetsConditionsToShowApprovalDialog(userId);
|
||||
if (userIsAvailableToViewDialog) {
|
||||
// Send message to open dialog immediately for this request
|
||||
this.messagingService.send("openLoginApproval");
|
||||
}
|
||||
}
|
||||
|
||||
async userMeetsConditionsToShowApprovalDialog(userId: UserId): Promise<boolean> {
|
||||
const authStatus = await firstValueFrom(this.authService.activeAccountStatus$);
|
||||
const activeUserId: UserId | null = await firstValueFrom(
|
||||
this.accountService.activeAccount$.pipe(getOptionalUserId),
|
||||
);
|
||||
const forceSetPasswordReason = await firstValueFrom(
|
||||
this.masterPasswordService.forceSetPasswordReason$(userId),
|
||||
);
|
||||
|
||||
const meetsConditions =
|
||||
authStatus === AuthenticationStatus.Unlocked &&
|
||||
activeUserId === userId &&
|
||||
forceSetPasswordReason === ForceSetPasswordReason.None;
|
||||
|
||||
return meetsConditions;
|
||||
}
|
||||
|
||||
async processPendingAuthRequests(): Promise<void> {
|
||||
// Prune any stale pending requests (older than 15 minutes)
|
||||
// This comes from GlobalSettings.cs
|
||||
// public TimeSpan UserRequestExpiration { get; set; } = TimeSpan.FromMinutes(15);
|
||||
const fifteenMinutesMs = 15 * 60 * 1000;
|
||||
|
||||
await this.pendingAuthRequestsState.pruneOlderThan(fifteenMinutesMs);
|
||||
|
||||
const pendingAuthRequestsInState: PendingAuthUserMarker[] =
|
||||
(await firstValueFrom(this.pendingAuthRequestsState.getAll$())) ?? [];
|
||||
|
||||
if (pendingAuthRequestsInState.length > 0) {
|
||||
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
const pendingAuthRequestsForActiveUser = pendingAuthRequestsInState.some(
|
||||
(e) => e.userId === activeUserId,
|
||||
);
|
||||
|
||||
if (pendingAuthRequestsForActiveUser) {
|
||||
this.messagingService.send("openLoginApproval");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,15 @@
|
||||
import { SystemNotificationEvent } from "@bitwarden/common/platform/system-notifications/system-notifications.service";
|
||||
import { UserId } from "@bitwarden/user-core";
|
||||
|
||||
import { AuthRequestAnsweringServiceAbstraction } from "../../abstractions/auth-request-answering/auth-request-answering.service.abstraction";
|
||||
import { AuthRequestAnsweringService } from "../../abstractions/auth-request-answering/auth-request-answering.service.abstraction";
|
||||
|
||||
export class NoopAuthRequestAnsweringService implements AuthRequestAnsweringServiceAbstraction {
|
||||
export class NoopAuthRequestAnsweringService implements AuthRequestAnsweringService {
|
||||
constructor() {}
|
||||
|
||||
async receivedPendingAuthRequest(userId: UserId, notificationId: string): Promise<void> {}
|
||||
|
||||
async handleAuthRequestNotificationClicked(event: SystemNotificationEvent): Promise<void> {}
|
||||
async userMeetsConditionsToShowApprovalDialog(userId: UserId): Promise<boolean> {
|
||||
return false;
|
||||
}
|
||||
|
||||
async processPendingAuthRequests(): Promise<void> {}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { LogoutReason } from "@bitwarden/auth/common";
|
||||
import { AuthRequestAnsweringServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth-request-answering/auth-request-answering.service.abstraction";
|
||||
import { AuthRequestAnsweringService } from "@bitwarden/common/auth/abstractions/auth-request-answering/auth-request-answering.service.abstraction";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { trackedMerge } from "@bitwarden/common/platform/misc";
|
||||
|
||||
@@ -64,7 +64,7 @@ export class DefaultServerNotificationsService implements ServerNotificationsSer
|
||||
private readonly signalRConnectionService: SignalRConnectionService,
|
||||
private readonly authService: AuthService,
|
||||
private readonly webPushConnectionService: WebPushConnectionService,
|
||||
private readonly authRequestAnsweringService: AuthRequestAnsweringServiceAbstraction,
|
||||
private readonly authRequestAnsweringService: AuthRequestAnsweringService,
|
||||
private readonly configService: ConfigService,
|
||||
) {
|
||||
this.notifications$ = this.configService
|
||||
@@ -293,9 +293,9 @@ export class DefaultServerNotificationsService implements ServerNotificationsSer
|
||||
* pending auth request to process at a time, so this second call will not cause any
|
||||
* duplicate processing conflicts on Extension.
|
||||
*/
|
||||
this.messagingService.send("openLoginApproval", {
|
||||
notificationId: notification.payload.id,
|
||||
});
|
||||
// this.messagingService.send("openLoginApproval", {
|
||||
// notificationId: notification.payload.id,
|
||||
// });
|
||||
break;
|
||||
case NotificationType.SyncOrganizationStatusChanged:
|
||||
await this.syncService.fullSync(true);
|
||||
|
||||
Reference in New Issue
Block a user