mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 06:13:38 +00:00
refactor(device-trust-toasts): [Auth/PM-11225] Refactor Toasts from Auth Services (#13665)
Refactor toast calls out of auth services. Toasts are now triggered by an observable emission that gets picked up by an observable pipeline in a new `DeviceTrustToastService` (libs/angular). That observable pipeline is then subscribed by by consuming the `AppComponent` for each client.
This commit is contained in:
@@ -1,9 +1,11 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit, inject } from "@angular/core";
|
import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit, inject } from "@angular/core";
|
||||||
|
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||||
import { NavigationEnd, Router, RouterOutlet } from "@angular/router";
|
import { NavigationEnd, Router, RouterOutlet } from "@angular/router";
|
||||||
import { Subject, takeUntil, firstValueFrom, concatMap, filter, tap } from "rxjs";
|
import { Subject, takeUntil, firstValueFrom, concatMap, filter, tap } from "rxjs";
|
||||||
|
|
||||||
|
import { DeviceTrustToastService } from "@bitwarden/angular/auth/services/device-trust-toast.service.abstraction";
|
||||||
import { LogoutReason } from "@bitwarden/auth/common";
|
import { LogoutReason } from "@bitwarden/auth/common";
|
||||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||||
@@ -68,7 +70,10 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
private animationControlService: AnimationControlService,
|
private animationControlService: AnimationControlService,
|
||||||
private biometricStateService: BiometricStateService,
|
private biometricStateService: BiometricStateService,
|
||||||
private biometricsService: BiometricsService,
|
private biometricsService: BiometricsService,
|
||||||
) {}
|
private deviceTrustToastService: DeviceTrustToastService,
|
||||||
|
) {
|
||||||
|
this.deviceTrustToastService.setupListeners$.pipe(takeUntilDestroyed()).subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
initPopupClosedListener();
|
initPopupClosedListener();
|
||||||
|
|||||||
@@ -10,10 +10,12 @@ import {
|
|||||||
ViewChild,
|
ViewChild,
|
||||||
ViewContainerRef,
|
ViewContainerRef,
|
||||||
} from "@angular/core";
|
} from "@angular/core";
|
||||||
|
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||||
import { Router } from "@angular/router";
|
import { Router } from "@angular/router";
|
||||||
import { filter, firstValueFrom, map, Subject, takeUntil, timeout, withLatestFrom } from "rxjs";
|
import { filter, firstValueFrom, map, Subject, takeUntil, timeout, withLatestFrom } from "rxjs";
|
||||||
|
|
||||||
import { CollectionService } from "@bitwarden/admin-console/common";
|
import { CollectionService } from "@bitwarden/admin-console/common";
|
||||||
|
import { DeviceTrustToastService } from "@bitwarden/angular/auth/services/device-trust-toast.service.abstraction";
|
||||||
import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref";
|
import { ModalRef } from "@bitwarden/angular/components/modal/modal.ref";
|
||||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||||
import { FingerprintDialogComponent, LoginApprovalComponent } from "@bitwarden/auth/angular";
|
import { FingerprintDialogComponent, LoginApprovalComponent } from "@bitwarden/auth/angular";
|
||||||
@@ -157,7 +159,10 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||||||
private stateEventRunnerService: StateEventRunnerService,
|
private stateEventRunnerService: StateEventRunnerService,
|
||||||
private accountService: AccountService,
|
private accountService: AccountService,
|
||||||
private organizationService: OrganizationService,
|
private organizationService: OrganizationService,
|
||||||
) {}
|
private deviceTrustToastService: DeviceTrustToastService,
|
||||||
|
) {
|
||||||
|
this.deviceTrustToastService.setupListeners$.pipe(takeUntilDestroyed()).subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.accountService.activeAccount$.pipe(takeUntil(this.destroy$)).subscribe((account) => {
|
this.accountService.activeAccount$.pipe(takeUntil(this.destroy$)).subscribe((account) => {
|
||||||
|
|||||||
@@ -2,11 +2,13 @@
|
|||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { DOCUMENT } from "@angular/common";
|
import { DOCUMENT } from "@angular/common";
|
||||||
import { Component, Inject, NgZone, OnDestroy, OnInit } from "@angular/core";
|
import { Component, Inject, NgZone, OnDestroy, OnInit } from "@angular/core";
|
||||||
|
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||||
import { NavigationEnd, Router } from "@angular/router";
|
import { NavigationEnd, Router } from "@angular/router";
|
||||||
import * as jq from "jquery";
|
import * as jq from "jquery";
|
||||||
import { Subject, filter, firstValueFrom, map, takeUntil, timeout } from "rxjs";
|
import { Subject, filter, firstValueFrom, map, takeUntil, timeout } from "rxjs";
|
||||||
|
|
||||||
import { CollectionService } from "@bitwarden/admin-console/common";
|
import { CollectionService } from "@bitwarden/admin-console/common";
|
||||||
|
import { DeviceTrustToastService } from "@bitwarden/angular/auth/services/device-trust-toast.service.abstraction";
|
||||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||||
import { EventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service";
|
import { EventUploadService } from "@bitwarden/common/abstractions/event/event-upload.service";
|
||||||
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
import { SearchService } from "@bitwarden/common/abstractions/search.service";
|
||||||
@@ -95,7 +97,10 @@ export class AppComponent implements OnDestroy, OnInit {
|
|||||||
private apiService: ApiService,
|
private apiService: ApiService,
|
||||||
private appIdService: AppIdService,
|
private appIdService: AppIdService,
|
||||||
private processReloadService: ProcessReloadServiceAbstraction,
|
private processReloadService: ProcessReloadServiceAbstraction,
|
||||||
) {}
|
private deviceTrustToastService: DeviceTrustToastService,
|
||||||
|
) {
|
||||||
|
this.deviceTrustToastService.setupListeners$.pipe(takeUntilDestroyed()).subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.i18nService.locale$.pipe(takeUntil(this.destroy$)).subscribe((locale) => {
|
this.i18nService.locale$.pipe(takeUntil(this.destroy$)).subscribe((locale) => {
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { Observable } from "rxjs";
|
||||||
|
|
||||||
|
export abstract class DeviceTrustToastService {
|
||||||
|
/**
|
||||||
|
* An observable pipeline that observes any cross-application toast messages
|
||||||
|
* that need to be shown as part of the trusted device encryption (TDE) process.
|
||||||
|
*/
|
||||||
|
abstract setupListeners$: Observable<void>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { merge, Observable, tap } from "rxjs";
|
||||||
|
|
||||||
|
import { AuthRequestServiceAbstraction } from "@bitwarden/auth/common";
|
||||||
|
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction";
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
import { ToastService } from "@bitwarden/components";
|
||||||
|
|
||||||
|
import { DeviceTrustToastService as DeviceTrustToastServiceAbstraction } from "./device-trust-toast.service.abstraction";
|
||||||
|
|
||||||
|
export class DeviceTrustToastService implements DeviceTrustToastServiceAbstraction {
|
||||||
|
private adminLoginApproved$: Observable<void>;
|
||||||
|
private deviceTrusted$: Observable<void>;
|
||||||
|
|
||||||
|
setupListeners$: Observable<void>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private authRequestService: AuthRequestServiceAbstraction,
|
||||||
|
private deviceTrustService: DeviceTrustServiceAbstraction,
|
||||||
|
private i18nService: I18nService,
|
||||||
|
private toastService: ToastService,
|
||||||
|
) {
|
||||||
|
this.adminLoginApproved$ = this.authRequestService.adminLoginApproved$.pipe(
|
||||||
|
tap(() => {
|
||||||
|
this.toastService.showToast({
|
||||||
|
variant: "success",
|
||||||
|
title: "",
|
||||||
|
message: this.i18nService.t("loginApproved"),
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.deviceTrusted$ = this.deviceTrustService.deviceTrusted$.pipe(
|
||||||
|
tap(() => {
|
||||||
|
this.toastService.showToast({
|
||||||
|
variant: "success",
|
||||||
|
title: "",
|
||||||
|
message: this.i18nService.t("deviceTrusted"),
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.setupListeners$ = merge(this.adminLoginApproved$, this.deviceTrusted$);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
import { mock, MockProxy } from "jest-mock-extended";
|
||||||
|
import { EMPTY, of } from "rxjs";
|
||||||
|
|
||||||
|
import { AuthRequestServiceAbstraction } from "@bitwarden/auth/common";
|
||||||
|
import { DeviceTrustServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust.service.abstraction";
|
||||||
|
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||||
|
import { ToastService } from "@bitwarden/components";
|
||||||
|
|
||||||
|
import { DeviceTrustToastService as DeviceTrustToastServiceAbstraction } from "./device-trust-toast.service.abstraction";
|
||||||
|
import { DeviceTrustToastService } from "./device-trust-toast.service.implementation";
|
||||||
|
|
||||||
|
describe("DeviceTrustToastService", () => {
|
||||||
|
let authRequestService: MockProxy<AuthRequestServiceAbstraction>;
|
||||||
|
let deviceTrustService: MockProxy<DeviceTrustServiceAbstraction>;
|
||||||
|
let i18nService: MockProxy<I18nService>;
|
||||||
|
let toastService: MockProxy<ToastService>;
|
||||||
|
|
||||||
|
let sut: DeviceTrustToastServiceAbstraction;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
authRequestService = mock<AuthRequestServiceAbstraction>();
|
||||||
|
deviceTrustService = mock<DeviceTrustServiceAbstraction>();
|
||||||
|
i18nService = mock<I18nService>();
|
||||||
|
toastService = mock<ToastService>();
|
||||||
|
|
||||||
|
i18nService.t.mockImplementation((key: string) => key); // just return the key that was given
|
||||||
|
});
|
||||||
|
|
||||||
|
const initService = () => {
|
||||||
|
return new DeviceTrustToastService(
|
||||||
|
authRequestService,
|
||||||
|
deviceTrustService,
|
||||||
|
i18nService,
|
||||||
|
toastService,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const loginApprovalToastOptions = {
|
||||||
|
variant: "success",
|
||||||
|
title: "",
|
||||||
|
message: "loginApproved",
|
||||||
|
};
|
||||||
|
|
||||||
|
const deviceTrustedToastOptions = {
|
||||||
|
variant: "success",
|
||||||
|
title: "",
|
||||||
|
message: "deviceTrusted",
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("setupListeners$", () => {
|
||||||
|
describe("given adminLoginApproved$ emits and deviceTrusted$ emits", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Arrange
|
||||||
|
authRequestService.adminLoginApproved$ = of(undefined);
|
||||||
|
deviceTrustService.deviceTrusted$ = of(undefined);
|
||||||
|
sut = initService();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should trigger a toast for login approval", (done) => {
|
||||||
|
// Act
|
||||||
|
sut.setupListeners$.subscribe({
|
||||||
|
complete: () => {
|
||||||
|
expect(toastService.showToast).toHaveBeenCalledWith(loginApprovalToastOptions); // Assert
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should trigger a toast for device trust", (done) => {
|
||||||
|
// Act
|
||||||
|
sut.setupListeners$.subscribe({
|
||||||
|
complete: () => {
|
||||||
|
expect(toastService.showToast).toHaveBeenCalledWith(deviceTrustedToastOptions); // Assert
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("given adminLoginApproved$ emits and deviceTrusted$ does not emit", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Arrange
|
||||||
|
authRequestService.adminLoginApproved$ = of(undefined);
|
||||||
|
deviceTrustService.deviceTrusted$ = EMPTY;
|
||||||
|
sut = initService();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should trigger a toast for login approval", (done) => {
|
||||||
|
// Act
|
||||||
|
sut.setupListeners$.subscribe({
|
||||||
|
complete: () => {
|
||||||
|
expect(toastService.showToast).toHaveBeenCalledWith(loginApprovalToastOptions); // Assert
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should NOT trigger a toast for device trust", (done) => {
|
||||||
|
// Act
|
||||||
|
sut.setupListeners$.subscribe({
|
||||||
|
complete: () => {
|
||||||
|
expect(toastService.showToast).not.toHaveBeenCalledWith(deviceTrustedToastOptions); // Assert
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("given adminLoginApproved$ does not emit and deviceTrusted$ emits", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Arrange
|
||||||
|
authRequestService.adminLoginApproved$ = EMPTY;
|
||||||
|
deviceTrustService.deviceTrusted$ = of(undefined);
|
||||||
|
sut = initService();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should NOT trigger a toast for login approval", (done) => {
|
||||||
|
// Act
|
||||||
|
sut.setupListeners$.subscribe({
|
||||||
|
complete: () => {
|
||||||
|
expect(toastService.showToast).not.toHaveBeenCalledWith(loginApprovalToastOptions); // Assert
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should trigger a toast for device trust", (done) => {
|
||||||
|
// Act
|
||||||
|
sut.setupListeners$.subscribe({
|
||||||
|
complete: () => {
|
||||||
|
expect(toastService.showToast).toHaveBeenCalledWith(deviceTrustedToastOptions); // Assert
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("given adminLoginApproved$ does not emit and deviceTrusted$ does not emit", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Arrange
|
||||||
|
authRequestService.adminLoginApproved$ = EMPTY;
|
||||||
|
deviceTrustService.deviceTrusted$ = EMPTY;
|
||||||
|
sut = initService();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should NOT trigger a toast for login approval", (done) => {
|
||||||
|
// Act
|
||||||
|
sut.setupListeners$.subscribe({
|
||||||
|
complete: () => {
|
||||||
|
expect(toastService.showToast).not.toHaveBeenCalledWith(loginApprovalToastOptions); // Assert
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should NOT trigger a toast for device trust", (done) => {
|
||||||
|
// Act
|
||||||
|
sut.setupListeners$.subscribe({
|
||||||
|
complete: () => {
|
||||||
|
expect(toastService.showToast).not.toHaveBeenCalledWith(deviceTrustedToastOptions); // Assert
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -317,6 +317,8 @@ import {
|
|||||||
IndividualVaultExportServiceAbstraction,
|
IndividualVaultExportServiceAbstraction,
|
||||||
} from "@bitwarden/vault-export-core";
|
} from "@bitwarden/vault-export-core";
|
||||||
|
|
||||||
|
import { DeviceTrustToastService as DeviceTrustToastServiceAbstraction } from "../auth/services/device-trust-toast.service.abstraction";
|
||||||
|
import { DeviceTrustToastService } from "../auth/services/device-trust-toast.service.implementation";
|
||||||
import { FormValidationErrorsService as FormValidationErrorsServiceAbstraction } from "../platform/abstractions/form-validation-errors.service";
|
import { FormValidationErrorsService as FormValidationErrorsServiceAbstraction } from "../platform/abstractions/form-validation-errors.service";
|
||||||
import { ViewCacheService } from "../platform/abstractions/view-cache.service";
|
import { ViewCacheService } from "../platform/abstractions/view-cache.service";
|
||||||
import { FormValidationErrorsService } from "../platform/services/form-validation-errors.service";
|
import { FormValidationErrorsService } from "../platform/services/form-validation-errors.service";
|
||||||
@@ -1463,6 +1465,16 @@ const safeProviders: SafeProvider[] = [
|
|||||||
useClass: DefaultTaskService,
|
useClass: DefaultTaskService,
|
||||||
deps: [StateProvider, ApiServiceAbstraction, OrganizationServiceAbstraction, ConfigService],
|
deps: [StateProvider, ApiServiceAbstraction, OrganizationServiceAbstraction, ConfigService],
|
||||||
}),
|
}),
|
||||||
|
safeProvider({
|
||||||
|
provide: DeviceTrustToastServiceAbstraction,
|
||||||
|
useClass: DeviceTrustToastService,
|
||||||
|
deps: [
|
||||||
|
AuthRequestServiceAbstraction,
|
||||||
|
DeviceTrustServiceAbstraction,
|
||||||
|
I18nServiceAbstraction,
|
||||||
|
ToastService,
|
||||||
|
],
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ export abstract class AuthRequestServiceAbstraction {
|
|||||||
/** Emits an auth request id when an auth request has been approved. */
|
/** Emits an auth request id when an auth request has been approved. */
|
||||||
authRequestPushNotification$: Observable<string>;
|
authRequestPushNotification$: Observable<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits when a login has been approved by an admin. This emission is specifically for the
|
||||||
|
* purpose of notifying the consuming component to display a toast informing the user.
|
||||||
|
*/
|
||||||
|
adminLoginApproved$: Observable<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an admin auth request for the given user if it exists.
|
* Returns an admin auth request for the given user if it exists.
|
||||||
* @param userId The user id.
|
* @param userId The user id.
|
||||||
@@ -106,4 +112,13 @@ export abstract class AuthRequestServiceAbstraction {
|
|||||||
* @returns The dash-delimited fingerprint phrase.
|
* @returns The dash-delimited fingerprint phrase.
|
||||||
*/
|
*/
|
||||||
abstract getFingerprintPhrase(email: string, publicKey: Uint8Array): Promise<string>;
|
abstract getFingerprintPhrase(email: string, publicKey: Uint8Array): Promise<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Passes a value to the adminLoginApprovedSubject via next(), which causes the
|
||||||
|
* adminLoginApproved$ observable to emit.
|
||||||
|
*
|
||||||
|
* The purpose is to notify consuming components (of adminLoginApproved$) to display
|
||||||
|
* a toast informing the user that a login has been approved by an admin.
|
||||||
|
*/
|
||||||
|
abstract emitAdminLoginApproved(): void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -278,7 +278,8 @@ export class SsoLoginStrategy extends LoginStrategy {
|
|||||||
// TODO: eventually we post and clean up DB as well once consumed on client
|
// TODO: eventually we post and clean up DB as well once consumed on client
|
||||||
await this.authRequestService.clearAdminAuthRequest(userId);
|
await this.authRequestService.clearAdminAuthRequest(userId);
|
||||||
|
|
||||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("loginApproved"));
|
// This notification will be picked up by the SsoComponent to handle displaying a toast to the user
|
||||||
|
this.authRequestService.emitAdminLoginApproved();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ export class AuthRequestService implements AuthRequestServiceAbstraction {
|
|||||||
private authRequestPushNotificationSubject = new Subject<string>();
|
private authRequestPushNotificationSubject = new Subject<string>();
|
||||||
authRequestPushNotification$: Observable<string>;
|
authRequestPushNotification$: Observable<string>;
|
||||||
|
|
||||||
|
// Observable emission is used to trigger a toast in consuming components
|
||||||
|
private adminLoginApprovedSubject = new Subject<void>();
|
||||||
|
adminLoginApproved$: Observable<void>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private appIdService: AppIdService,
|
private appIdService: AppIdService,
|
||||||
private accountService: AccountService,
|
private accountService: AccountService,
|
||||||
@@ -53,6 +57,7 @@ export class AuthRequestService implements AuthRequestServiceAbstraction {
|
|||||||
private stateProvider: StateProvider,
|
private stateProvider: StateProvider,
|
||||||
) {
|
) {
|
||||||
this.authRequestPushNotification$ = this.authRequestPushNotificationSubject.asObservable();
|
this.authRequestPushNotification$ = this.authRequestPushNotificationSubject.asObservable();
|
||||||
|
this.adminLoginApproved$ = this.adminLoginApprovedSubject.asObservable();
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAdminAuthRequest(userId: UserId): Promise<AdminAuthRequestStorable | null> {
|
async getAdminAuthRequest(userId: UserId): Promise<AdminAuthRequestStorable | null> {
|
||||||
@@ -207,4 +212,8 @@ export class AuthRequestService implements AuthRequestServiceAbstraction {
|
|||||||
async getFingerprintPhrase(email: string, publicKey: Uint8Array): Promise<string> {
|
async getFingerprintPhrase(email: string, publicKey: Uint8Array): Promise<string> {
|
||||||
return (await this.keyService.getFingerprint(email.toLowerCase(), publicKey)).join("-");
|
return (await this.keyService.getFingerprint(email.toLowerCase(), publicKey)).join("-");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emitAdminLoginApproved(): void {
|
||||||
|
this.adminLoginApprovedSubject.next();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ export abstract class DeviceTrustServiceAbstraction {
|
|||||||
*/
|
*/
|
||||||
supportsDeviceTrust$: Observable<boolean>;
|
supportsDeviceTrust$: Observable<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits when a device has been trusted. This emission is specifically for the purpose of notifying
|
||||||
|
* the consuming component to display a toast informing the user the device has been trusted.
|
||||||
|
*/
|
||||||
|
deviceTrusted$: Observable<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Checks if the device trust feature is supported for the given user.
|
* @description Checks if the device trust feature is supported for the given user.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// FIXME: Update this file to be type safe and remove this and next line
|
// FIXME: Update this file to be type safe and remove this and next line
|
||||||
// @ts-strict-ignore
|
// @ts-strict-ignore
|
||||||
import { firstValueFrom, map, Observable } from "rxjs";
|
import { firstValueFrom, map, Observable, Subject } from "rxjs";
|
||||||
|
|
||||||
import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
|
import { UserDecryptionOptionsServiceAbstraction } from "@bitwarden/auth/common";
|
||||||
import { KeyService } from "@bitwarden/key-management";
|
import { KeyService } from "@bitwarden/key-management";
|
||||||
@@ -63,6 +63,10 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction {
|
|||||||
|
|
||||||
supportsDeviceTrust$: Observable<boolean>;
|
supportsDeviceTrust$: Observable<boolean>;
|
||||||
|
|
||||||
|
// Observable emission is used to trigger a toast in consuming components
|
||||||
|
private deviceTrustedSubject = new Subject<void>();
|
||||||
|
deviceTrusted$ = this.deviceTrustedSubject.asObservable();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private keyGenerationService: KeyGenerationService,
|
private keyGenerationService: KeyGenerationService,
|
||||||
private cryptoFunctionService: CryptoFunctionService,
|
private cryptoFunctionService: CryptoFunctionService,
|
||||||
@@ -177,7 +181,8 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction {
|
|||||||
// store device key in local/secure storage if enc keys posted to server successfully
|
// store device key in local/secure storage if enc keys posted to server successfully
|
||||||
await this.setDeviceKey(userId, deviceKey);
|
await this.setDeviceKey(userId, deviceKey);
|
||||||
|
|
||||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("deviceTrusted"));
|
// This emission will be picked up by consuming components to handle displaying a toast to the user
|
||||||
|
this.deviceTrustedSubject.next();
|
||||||
|
|
||||||
return deviceResponse;
|
return deviceResponse;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user