mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 13:23:34 +00:00
[PM-14421] Access Intelligence: Introduce At-risk Passwords Page (#13044)
* [PM-14421] Add initial at risk password page component and route * [PM-14421] Add new at-risk-password guard and update task service to consider feature flag for tasksEnabled$ * [PM-14421] Export vault observable utilities to be used outside of libs/vault * [PM-14421] Implement at risk passwords page * [PM-14421] Add temporary callout for at-risk tasks to browser vault view * [PM-14421] Fix service registration after merge * [PM-14421] Fix organization service usage after merge * [PM-14421] Add autofill setting callout * [PM-14421] Fix failing test * [PM-14421] Change autofill setting check and toggle * [PM-14421] Make autofill setting callout dismissal persistent * [PM-14421] Fix tests * [PM-14421] Fix button structure * [PM-14421] Handle plural tasks i18n * [PM-14421] Fix cipher service usage after refactor on main * [PM-14421] Fix at-risk-password spec file
This commit is contained in:
@@ -296,7 +296,12 @@ import {
|
||||
DefaultUserAsymmetricKeysRegenerationApiService,
|
||||
} from "@bitwarden/key-management";
|
||||
import { SafeInjectionToken } from "@bitwarden/ui-common";
|
||||
import { NewDeviceVerificationNoticeService, PasswordRepromptService } from "@bitwarden/vault";
|
||||
import {
|
||||
DefaultTaskService,
|
||||
NewDeviceVerificationNoticeService,
|
||||
PasswordRepromptService,
|
||||
TaskService,
|
||||
} from "@bitwarden/vault";
|
||||
import {
|
||||
VaultExportService,
|
||||
VaultExportServiceAbstraction,
|
||||
@@ -1463,6 +1468,11 @@ const safeProviders: SafeProvider[] = [
|
||||
useClass: PasswordLoginStrategyData,
|
||||
deps: [],
|
||||
}),
|
||||
safeProvider({
|
||||
provide: TaskService,
|
||||
useClass: DefaultTaskService,
|
||||
deps: [StateProvider, ApiServiceAbstraction, OrganizationServiceAbstraction, ConfigService],
|
||||
}),
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
||||
@@ -5,6 +5,8 @@ export { CopyCipherFieldDirective } from "./components/copy-cipher-field.directi
|
||||
export { OrgIconDirective } from "./components/org-icon.directive";
|
||||
export { CanDeleteCipherDirective } from "./components/can-delete-cipher.directive";
|
||||
|
||||
export * from "./utils/observable-utilities";
|
||||
|
||||
export * from "./cipher-view";
|
||||
export * from "./cipher-form";
|
||||
export {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { BehaviorSubject, firstValueFrom } from "rxjs";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { StateProvider } from "@bitwarden/common/platform/state";
|
||||
import { SecurityTaskId, UserId } from "@bitwarden/common/types/guid";
|
||||
import { DefaultTaskService, SecurityTaskStatus } from "@bitwarden/vault";
|
||||
@@ -18,18 +19,26 @@ describe("Default task service", () => {
|
||||
|
||||
const mockApiSend = jest.fn();
|
||||
const mockGetAllOrgs$ = jest.fn();
|
||||
const mockGetFeatureFlag$ = jest.fn();
|
||||
|
||||
let testBed: TestBed;
|
||||
|
||||
beforeEach(async () => {
|
||||
mockApiSend.mockClear();
|
||||
mockGetAllOrgs$.mockClear();
|
||||
mockGetFeatureFlag$.mockClear();
|
||||
|
||||
fakeStateProvider = new FakeStateProvider(mockAccountServiceWith("user-id" as UserId));
|
||||
testBed = TestBed.configureTestingModule({
|
||||
imports: [],
|
||||
providers: [
|
||||
DefaultTaskService,
|
||||
{
|
||||
provide: ConfigService,
|
||||
useValue: {
|
||||
getFeatureFlag$: mockGetFeatureFlag$,
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: StateProvider,
|
||||
useValue: fakeStateProvider,
|
||||
@@ -52,6 +61,7 @@ describe("Default task service", () => {
|
||||
|
||||
describe("tasksEnabled$", () => {
|
||||
it("should emit true if any organization uses risk insights", async () => {
|
||||
mockGetFeatureFlag$.mockReturnValue(new BehaviorSubject(true));
|
||||
mockGetAllOrgs$.mockReturnValue(
|
||||
new BehaviorSubject([
|
||||
{
|
||||
@@ -71,6 +81,7 @@ describe("Default task service", () => {
|
||||
});
|
||||
|
||||
it("should emit false if no organization uses risk insights", async () => {
|
||||
mockGetFeatureFlag$.mockReturnValue(new BehaviorSubject(true));
|
||||
mockGetAllOrgs$.mockReturnValue(
|
||||
new BehaviorSubject([
|
||||
{
|
||||
@@ -88,6 +99,23 @@ describe("Default task service", () => {
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("should emit false if the feature flag is off", async () => {
|
||||
mockGetFeatureFlag$.mockReturnValue(new BehaviorSubject(false));
|
||||
mockGetAllOrgs$.mockReturnValue(
|
||||
new BehaviorSubject([
|
||||
{
|
||||
useRiskInsights: true,
|
||||
},
|
||||
] as Organization[]),
|
||||
);
|
||||
|
||||
const { tasksEnabled$ } = testBed.inject(DefaultTaskService);
|
||||
|
||||
const result = await firstValueFrom(tasksEnabled$("user-id" as UserId));
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("tasks$", () => {
|
||||
@@ -100,7 +128,7 @@ describe("Default task service", () => {
|
||||
] as SecurityTaskResponse[],
|
||||
});
|
||||
|
||||
fakeStateProvider.singleUser.mockFor("user-id" as UserId, SECURITY_TASKS, null);
|
||||
fakeStateProvider.singleUser.mockFor("user-id" as UserId, SECURITY_TASKS, null as any);
|
||||
|
||||
const { tasks$ } = testBed.inject(DefaultTaskService);
|
||||
|
||||
@@ -183,7 +211,11 @@ describe("Default task service", () => {
|
||||
] as SecurityTaskResponse[],
|
||||
});
|
||||
|
||||
const mock = fakeStateProvider.singleUser.mockFor("user-id" as UserId, SECURITY_TASKS, null);
|
||||
const mock = fakeStateProvider.singleUser.mockFor(
|
||||
"user-id" as UserId,
|
||||
SECURITY_TASKS,
|
||||
null as any,
|
||||
);
|
||||
|
||||
const service = testBed.inject(DefaultTaskService);
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { map, switchMap } from "rxjs";
|
||||
import { combineLatest, map, switchMap } from "rxjs";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ListResponse } from "@bitwarden/common/models/response/list.response";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { StateProvider } from "@bitwarden/common/platform/state";
|
||||
import { SecurityTaskId, UserId } from "@bitwarden/common/types/guid";
|
||||
import { SecurityTask, SecurityTaskStatus, TaskService } from "@bitwarden/vault";
|
||||
@@ -19,12 +21,16 @@ export class DefaultTaskService implements TaskService {
|
||||
private stateProvider: StateProvider,
|
||||
private apiService: ApiService,
|
||||
private organizationService: OrganizationService,
|
||||
private configService: ConfigService,
|
||||
) {}
|
||||
|
||||
tasksEnabled$ = perUserCache$((userId) => {
|
||||
return this.organizationService
|
||||
.organizations$(userId)
|
||||
.pipe(map((orgs) => orgs.some((o) => o.useRiskInsights)));
|
||||
return combineLatest([
|
||||
this.organizationService
|
||||
.organizations$(userId)
|
||||
.pipe(map((orgs) => orgs.some((o) => o.useRiskInsights))),
|
||||
this.configService.getFeatureFlag$(FeatureFlag.SecurityTasks),
|
||||
]).pipe(map(([atLeastOneOrgEnabled, flagEnabled]) => atLeastOneOrgEnabled && flagEnabled));
|
||||
});
|
||||
|
||||
tasks$ = perUserCache$((userId) => {
|
||||
|
||||
Reference in New Issue
Block a user