1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-14 15:33:55 +00:00

Merge branch 'main' into auth/pm-3387/invalid-auth-request-error

This commit is contained in:
rr-bw
2025-08-19 09:27:31 -07:00
19 changed files with 394 additions and 93 deletions

View File

@@ -29,7 +29,7 @@ describe("AutofillInlineMenuContentService", () => {
autofillInit = new AutofillInit(
domQueryService,
domElementVisibilityService,
null,
undefined,
autofillInlineMenuContentService,
);
autofillInit.init();
@@ -319,6 +319,8 @@ describe("AutofillInlineMenuContentService", () => {
describe("handleContainerElementMutationObserverUpdate", () => {
let mockMutationRecord: MockProxy<MutationRecord>;
let mockBodyMutationRecord: MockProxy<MutationRecord>;
let mockHTMLMutationRecord: MockProxy<MutationRecord>;
let buttonElement: HTMLElement;
let listElement: HTMLElement;
let isInlineMenuListVisibleSpy: jest.SpyInstance;
@@ -329,6 +331,16 @@ describe("AutofillInlineMenuContentService", () => {
<div class="overlay-list"></div>
`;
mockMutationRecord = mock<MutationRecord>({ target: globalThis.document.body } as any);
mockHTMLMutationRecord = mock<MutationRecord>({
target: globalThis.document.body.parentElement,
attributeName: "style",
type: "attributes",
} as any);
mockBodyMutationRecord = mock<MutationRecord>({
target: globalThis.document.body,
attributeName: "style",
type: "attributes",
} as any);
buttonElement = document.querySelector(".overlay-button") as HTMLElement;
listElement = document.querySelector(".overlay-list") as HTMLElement;
autofillInlineMenuContentService["buttonElement"] = buttonElement;
@@ -343,6 +355,7 @@ describe("AutofillInlineMenuContentService", () => {
"isTriggeringExcessiveMutationObserverIterations",
)
.mockReturnValue(false);
jest.spyOn(autofillInlineMenuContentService as any, "closeInlineMenu");
});
it("skips handling the mutation if the overlay elements are not present in the DOM", async () => {
@@ -373,6 +386,33 @@ describe("AutofillInlineMenuContentService", () => {
expect(globalThis.document.body.insertBefore).not.toHaveBeenCalled();
});
it("closes the inline menu if the page body is not sufficiently opaque", async () => {
document.querySelector("html").style.opacity = "0.9";
document.body.style.opacity = "0";
autofillInlineMenuContentService["handlePageMutations"]([mockBodyMutationRecord]);
expect(autofillInlineMenuContentService["pageIsOpaque"]).toBe(false);
expect(autofillInlineMenuContentService["closeInlineMenu"]).toHaveBeenCalled();
});
it("closes the inline menu if the page html is not sufficiently opaque", async () => {
document.querySelector("html").style.opacity = "0.3";
document.body.style.opacity = "0.7";
autofillInlineMenuContentService["handlePageMutations"]([mockHTMLMutationRecord]);
expect(autofillInlineMenuContentService["pageIsOpaque"]).toBe(false);
expect(autofillInlineMenuContentService["closeInlineMenu"]).toHaveBeenCalled();
});
it("does not close the inline menu if the page html and body is sufficiently opaque", async () => {
document.querySelector("html").style.opacity = "0.9";
document.body.style.opacity = "1";
autofillInlineMenuContentService["handlePageMutations"]([mockBodyMutationRecord]);
expect(autofillInlineMenuContentService["pageIsOpaque"]).toBe(true);
expect(autofillInlineMenuContentService["closeInlineMenu"]).not.toHaveBeenCalled();
});
it("skips re-arranging the DOM elements if the last child of the body is non-existent", async () => {
document.body.innerHTML = "";

View File

@@ -29,8 +29,11 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
private isFirefoxBrowser =
globalThis.navigator.userAgent.indexOf(" Firefox/") !== -1 ||
globalThis.navigator.userAgent.indexOf(" Gecko/") !== -1;
private buttonElement: HTMLElement;
private listElement: HTMLElement;
private buttonElement?: HTMLElement;
private listElement?: HTMLElement;
private htmlMutationObserver: MutationObserver;
private bodyMutationObserver: MutationObserver;
private pageIsOpaque = true;
private inlineMenuElementsMutationObserver: MutationObserver;
private containerElementMutationObserver: MutationObserver;
private mutationObserverIterations = 0;
@@ -49,6 +52,7 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
};
constructor() {
this.checkPageOpacity();
this.setupMutationObserver();
}
@@ -281,6 +285,9 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
* that the inline menu elements are always present at the bottom of the menu container.
*/
private setupMutationObserver = () => {
this.htmlMutationObserver = new MutationObserver(this.handlePageMutations);
this.bodyMutationObserver = new MutationObserver(this.handlePageMutations);
this.inlineMenuElementsMutationObserver = new MutationObserver(
this.handleInlineMenuElementMutationObserverUpdate,
);
@@ -295,6 +302,9 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
* elements are not modified by the website.
*/
private observeCustomElements() {
this.htmlMutationObserver?.observe(document.querySelector("html"), { attributes: true });
this.bodyMutationObserver?.observe(document.body, { attributes: true });
if (this.buttonElement) {
this.inlineMenuElementsMutationObserver?.observe(this.buttonElement, {
attributes: true,
@@ -395,11 +405,56 @@ export class AutofillInlineMenuContentService implements AutofillInlineMenuConte
});
};
private checkPageOpacity = () => {
this.pageIsOpaque = this.getPageIsOpaque();
if (!this.pageIsOpaque) {
this.closeInlineMenu();
}
};
private handlePageMutations = (mutations: MutationRecord[]) => {
for (const mutation of mutations) {
if (mutation.type === "attributes") {
this.checkPageOpacity();
}
}
};
/**
* Checks the opacity of the page body and body parent, since the inline menu experience
* will inherit the opacity, despite being otherwise encapsulated from styling changes
* of parents below the body. Assumes the target element will be a direct child of the page
* `body` (enforced elsewhere).
*/
private getPageIsOpaque() {
// These are computed style values, so we don't need to worry about non-float values
// for `opacity`, here
const htmlOpacity = globalThis.window.getComputedStyle(
globalThis.document.querySelector("html"),
).opacity;
const bodyOpacity = globalThis.window.getComputedStyle(
globalThis.document.querySelector("body"),
).opacity;
// Any value above this is considered "opaque" for our purposes
const opacityThreshold = 0.6;
return parseFloat(htmlOpacity) > opacityThreshold && parseFloat(bodyOpacity) > opacityThreshold;
}
/**
* Processes the mutation of the element that contains the inline menu. Will trigger when an
* idle moment in the execution of the main thread is detected.
*/
private processContainerElementMutation = async (containerElement: HTMLElement) => {
// If the computed opacity of the body and parent is not sufficiently opaque, tear
// down and prevent building the inline menu experience.
this.checkPageOpacity();
if (!this.pageIsOpaque) {
return;
}
const lastChild = containerElement.lastElementChild;
const secondToLastChild = lastChild?.previousElementSibling;
const lastChildIsInlineMenuList = lastChild === this.listElement;

View File

@@ -1,4 +1,4 @@
import { map, Observable } from "rxjs";
import { concat, defer, filter, map, merge, Observable, shareReplay, switchMap } from "rxjs";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
@@ -17,19 +17,48 @@ export interface RawBadgeState {
export interface BadgeBrowserApi {
activeTab$: Observable<chrome.tabs.TabActiveInfo | undefined>;
// activeTabs$: Observable<chrome.tabs.Tab[]>;
setState(state: RawBadgeState, tabId?: number): Promise<void>;
getTabs(): Promise<number[]>;
getActiveTabs(): Promise<chrome.tabs.Tab[]>;
}
export class DefaultBadgeBrowserApi implements BadgeBrowserApi {
private badgeAction = BrowserApi.getBrowserAction();
private sidebarAction = BrowserApi.getSidebarAction(self);
activeTab$ = fromChromeEvent(chrome.tabs.onActivated).pipe(
map(([tabActiveInfo]) => tabActiveInfo),
private onTabActivated$ = fromChromeEvent(chrome.tabs.onActivated).pipe(
switchMap(async ([activeInfo]) => activeInfo),
shareReplay({ bufferSize: 1, refCount: true }),
);
activeTab$ = concat(
defer(async () => {
const currentTab = await BrowserApi.getTabFromCurrentWindow();
if (currentTab == null || currentTab.id === undefined) {
return undefined;
}
return { tabId: currentTab.id, windowId: currentTab.windowId };
}),
merge(
this.onTabActivated$,
fromChromeEvent(chrome.tabs.onUpdated).pipe(
filter(
([_, changeInfo]) =>
// Only emit if the url was updated
changeInfo.url != undefined,
),
map(([tabId, _changeInfo, tab]) => ({ tabId, windowId: tab.windowId })),
),
),
).pipe(shareReplay({ bufferSize: 1, refCount: true }));
getActiveTabs(): Promise<chrome.tabs.Tab[]> {
return BrowserApi.getActiveTabs();
}
constructor(private platformUtilsService: PlatformUtilsService) {}
async setState(state: RawBadgeState, tabId?: number): Promise<void> {

View File

@@ -37,8 +37,9 @@ describe("BadgeService", () => {
describe("given a single tab is open", () => {
beforeEach(() => {
badgeApi.tabs = [1];
badgeApi.setActiveTab(tabId);
badgeApi.tabs = [tabId];
badgeApi.setActiveTabs([tabId]);
badgeApi.setLastActivatedTab(tabId);
badgeServiceSubscription = badgeService.startListening();
});
@@ -187,17 +188,18 @@ describe("BadgeService", () => {
});
});
describe("given multiple tabs are open", () => {
describe("given multiple tabs are open, only one active", () => {
const tabId = 1;
const tabIds = [1, 2, 3];
beforeEach(() => {
badgeApi.tabs = tabIds;
badgeApi.setActiveTab(tabId);
badgeApi.setActiveTabs([tabId]);
badgeApi.setLastActivatedTab(tabId);
badgeServiceSubscription = badgeService.startListening();
});
it("sets state for each tab when no other state has been set", async () => {
it("sets general state for active tab when no other state has been set", async () => {
const state: BadgeState = {
text: "text",
backgroundColor: "color",
@@ -213,6 +215,67 @@ describe("BadgeService", () => {
3: undefined,
});
});
it("only updates the active tab when setting state", async () => {
const state: BadgeState = {
text: "text",
backgroundColor: "color",
icon: BadgeIcon.Locked,
};
badgeApi.setState.mockReset();
await badgeService.setState("state-1", BadgeStatePriority.Default, state, tabId);
await badgeService.setState("state-2", BadgeStatePriority.Default, state, 2);
await badgeService.setState("state-2", BadgeStatePriority.Default, state, 2);
await new Promise((resolve) => setTimeout(resolve, 0));
expect(badgeApi.setState).toHaveBeenCalledTimes(1);
});
});
describe("given multiple tabs are open and multiple are active", () => {
const activeTabIds = [1, 2];
const tabIds = [1, 2, 3];
beforeEach(() => {
badgeApi.tabs = tabIds;
badgeApi.setActiveTabs(activeTabIds);
badgeApi.setLastActivatedTab(1);
badgeServiceSubscription = badgeService.startListening();
});
it("sets general state for active tabs when no other state has been set", async () => {
const state: BadgeState = {
text: "text",
backgroundColor: "color",
icon: BadgeIcon.Locked,
};
await badgeService.setState("state-name", BadgeStatePriority.Default, state);
await new Promise((resolve) => setTimeout(resolve, 0));
expect(badgeApi.specificStates).toEqual({
1: state,
2: state,
3: undefined,
});
});
it("only updates the active tabs when setting general state", async () => {
const state: BadgeState = {
text: "text",
backgroundColor: "color",
icon: BadgeIcon.Locked,
};
badgeApi.setState.mockReset();
await badgeService.setState("state-1", BadgeStatePriority.Default, state, 1);
await badgeService.setState("state-2", BadgeStatePriority.Default, state, 2);
await badgeService.setState("state-3", BadgeStatePriority.Default, state, 3);
await new Promise((resolve) => setTimeout(resolve, 0));
expect(badgeApi.setState).toHaveBeenCalledTimes(2);
});
});
});
@@ -222,7 +285,8 @@ describe("BadgeService", () => {
beforeEach(() => {
badgeApi.tabs = [tabId];
badgeApi.setActiveTab(tabId);
badgeApi.setActiveTabs([tabId]);
badgeApi.setLastActivatedTab(tabId);
badgeServiceSubscription = badgeService.startListening();
});
@@ -491,13 +555,14 @@ describe("BadgeService", () => {
});
});
describe("given multiple tabs are open", () => {
describe("given multiple tabs are open, only one active", () => {
const tabId = 1;
const tabIds = [1, 2, 3];
beforeEach(() => {
badgeApi.tabs = tabIds;
badgeApi.setActiveTab(tabId);
badgeApi.setActiveTabs([tabId]);
badgeApi.setLastActivatedTab(tabId);
badgeServiceSubscription = badgeService.startListening();
});
@@ -528,5 +593,62 @@ describe("BadgeService", () => {
});
});
});
describe("given multiple tabs are open and multiple are active", () => {
const tabId = 1;
const activeTabIds = [1, 2];
const tabIds = [1, 2, 3];
beforeEach(() => {
badgeApi.tabs = tabIds;
badgeApi.setActiveTabs(activeTabIds);
badgeApi.setLastActivatedTab(tabId);
badgeServiceSubscription = badgeService.startListening();
});
it("sets general state for all active tabs when no other state has been set", async () => {
const generalState: BadgeState = {
text: "general-text",
backgroundColor: "general-color",
icon: BadgeIcon.Unlocked,
};
await badgeService.setState("general-state", BadgeStatePriority.Default, generalState);
await new Promise((resolve) => setTimeout(resolve, 0));
expect(badgeApi.specificStates).toEqual({
[tabIds[0]]: generalState,
[tabIds[1]]: generalState,
[tabIds[2]]: undefined,
});
});
it("sets tab-specific state for provided tab", async () => {
const generalState: BadgeState = {
text: "general-text",
backgroundColor: "general-color",
icon: BadgeIcon.Unlocked,
};
const specificState: BadgeState = {
text: "tab-text",
icon: BadgeIcon.Locked,
};
await badgeService.setState("general-state", BadgeStatePriority.Default, generalState);
await badgeService.setState(
"tab-state",
BadgeStatePriority.Default,
specificState,
tabIds[0],
);
await new Promise((resolve) => setTimeout(resolve, 0));
expect(badgeApi.specificStates).toEqual({
[tabIds[0]]: { ...specificState, backgroundColor: "general-color" },
[tabIds[1]]: generalState,
[tabIds[2]]: undefined,
});
});
});
});
});

View File

@@ -1,13 +1,4 @@
import {
combineLatest,
concatMap,
distinctUntilChanged,
filter,
map,
pairwise,
startWith,
Subscription,
} from "rxjs";
import { concatMap, filter, Subscription, withLatestFrom } from "rxjs";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import {
@@ -17,7 +8,6 @@ import {
StateProvider,
} from "@bitwarden/common/platform/state";
import { difference } from "./array-utils";
import { BadgeBrowserApi, RawBadgeState } from "./badge-browser-api";
import { DefaultBadgeState } from "./consts";
import { BadgeStatePriority } from "./priority";
@@ -34,14 +24,14 @@ const BADGE_STATES = new KeyDefinition(BADGE_MEMORY, "badgeStates", {
});
export class BadgeService {
private states: GlobalState<Record<string, StateSetting>>;
private serviceState: GlobalState<Record<string, StateSetting>>;
constructor(
private stateProvider: StateProvider,
private badgeApi: BadgeBrowserApi,
private logService: LogService,
) {
this.states = this.stateProvider.getGlobal(BADGE_STATES);
this.serviceState = this.stateProvider.getGlobal(BADGE_STATES);
}
/**
@@ -49,44 +39,20 @@ export class BadgeService {
* Without this the service will not be able to update the badge state.
*/
startListening(): Subscription {
return combineLatest({
states: this.states.state$.pipe(
startWith({}),
distinctUntilChanged(),
map((states) => new Set(states ? Object.values(states) : [])),
pairwise(),
map(([previous, current]) => {
const [removed, added] = difference(previous, current);
return { all: current, removed, added };
}),
filter(({ removed, added }) => removed.size > 0 || added.size > 0),
),
activeTab: this.badgeApi.activeTab$.pipe(startWith(undefined)),
})
// React to tab changes
return this.badgeApi.activeTab$
.pipe(
concatMap(async ({ states, activeTab }) => {
const changed = [...states.removed, ...states.added];
// If the active tab wasn't changed, we don't need to update the badge.
if (!changed.some((s) => s.tabId === activeTab?.tabId || s.tabId === undefined)) {
return;
}
try {
const state = this.calculateState(states.all, activeTab?.tabId);
await this.badgeApi.setState(state, activeTab?.tabId);
} catch (error) {
// This usually happens when the user opens a popout because of how the browser treats it
// as a tab in the same window but then won't let you set the badge state for it.
this.logService.warning("Failed to set badge state", error);
}
withLatestFrom(this.serviceState.state$),
filter(([activeTab]) => activeTab != undefined),
concatMap(async ([activeTab, serviceState]) => {
await this.updateBadge(serviceState, activeTab!.tabId);
}),
)
.subscribe({
error: (err: unknown) => {
error: (error: unknown) => {
this.logService.error(
"Fatal error in badge service observable, badge will fail to update",
err,
error,
);
},
});
@@ -108,7 +74,12 @@ export class BadgeService {
* @param tabId Limit this badge state to a specific tab. If this is not set, the state will be applied to all tabs.
*/
async setState(name: string, priority: BadgeStatePriority, state: BadgeState, tabId?: number) {
await this.states.update((s) => ({ ...s, [name]: { priority, state, tabId } }));
const newServiceState = await this.serviceState.update((s) => ({
...s,
[name]: { priority, state, tabId },
}));
await this.updateBadge(newServiceState, tabId);
}
/**
@@ -120,11 +91,21 @@ export class BadgeService {
* @param name The name of the state to clear.
*/
async clearState(name: string) {
await this.states.update((s) => {
let clearedState: StateSetting | undefined;
const newServiceState = await this.serviceState.update((s) => {
clearedState = s?.[name];
const newStates = { ...s };
delete newStates[name];
return newStates;
});
if (clearedState === undefined) {
return;
}
// const activeTabs = await firstValueFrom(this.badgeApi.activeTabs$);
await this.updateBadge(newServiceState, clearedState.tabId);
}
private calculateState(states: Set<StateSetting>, tabId?: number): RawBadgeState {
@@ -159,6 +140,52 @@ export class BadgeService {
...mergedState,
};
}
/**
* Common function deduplicating the logic for updating the badge with the current state.
* This will only update the badge if the active tab is the same as the tabId of the latest change.
* If the active tab is not set, it will not update the badge.
*
* @param activeTab The currently active tab.
* @param serviceState The current state of the badge service. If this is null or undefined, an empty set will be assumed.
* @param tabId Tab id for which the the latest state change applied to. Set this to activeTab.tabId to force an update.
*/
private async updateBadge(
// activeTabs: chrome.tabs.Tab[],
serviceState: Record<string, StateSetting> | null | undefined,
tabId: number | undefined,
) {
const activeTabs = await this.badgeApi.getActiveTabs();
if (tabId !== undefined && !activeTabs.some((tab) => tab.id === tabId)) {
return; // No need to update the badge if the state is not for the active tab.
}
const tabIdsToUpdate = tabId ? [tabId] : activeTabs.map((tab) => tab.id);
for (const tabId of tabIdsToUpdate) {
if (tabId === undefined) {
continue; // Skip if tab id is undefined.
}
const newBadgeState = this.calculateState(new Set(Object.values(serviceState ?? {})), tabId);
try {
await this.badgeApi.setState(newBadgeState, tabId);
} catch (error) {
this.logService.error("Failed to set badge state", error);
}
}
if (tabId === undefined) {
// If no tabId was provided we should also update the general badge state
const newBadgeState = this.calculateState(new Set(Object.values(serviceState ?? {})));
try {
await this.badgeApi.setState(newBadgeState, tabId);
} catch (error) {
this.logService.error("Failed to set general badge state", error);
}
}
}
}
/**

View File

@@ -9,15 +9,33 @@ export class MockBadgeBrowserApi implements BadgeBrowserApi {
specificStates: Record<number, RawBadgeState> = {};
generalState?: RawBadgeState;
tabs: number[] = [];
activeTabs: number[] = [];
setActiveTab(tabId: number) {
getActiveTabs(): Promise<chrome.tabs.Tab[]> {
return Promise.resolve(
this.activeTabs.map(
(tabId) =>
({
id: tabId,
windowId: 1,
active: true,
}) as chrome.tabs.Tab,
),
);
}
setActiveTabs(tabs: number[]) {
this.activeTabs = tabs;
}
setLastActivatedTab(tabId: number) {
this._activeTab$.next({
tabId,
windowId: 1,
});
}
setState(state: RawBadgeState, tabId?: number): Promise<void> {
setState = jest.fn().mockImplementation((state: RawBadgeState, tabId?: number): Promise<void> => {
if (tabId !== undefined) {
this.specificStates[tabId] = state;
} else {
@@ -25,7 +43,7 @@ export class MockBadgeBrowserApi implements BadgeBrowserApi {
}
return Promise.resolve();
}
});
getTabs(): Promise<number[]> {
return Promise.resolve(this.tabs);

View File

@@ -32,6 +32,15 @@ export class BrowserApi {
return BrowserApi.manifestVersion === expectedVersion;
}
/**
* Gets all open browser windows, including their tabs.
*
* @returns A promise that resolves to an array of browser windows.
*/
static async getWindows(): Promise<chrome.windows.Window[]> {
return new Promise((resolve) => chrome.windows.getAll({ populate: true }, resolve));
}
/**
* Gets the current window or the window with the given id.
*

View File

@@ -1,10 +1,10 @@
import { mock } from "jest-mock-extended";
import { KeyGenerationService } from "@bitwarden/common/key-management/crypto";
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { FakeMasterPasswordService } from "@bitwarden/common/key-management/master-password/services/fake-master-password.service";
import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction";
import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";

View File

@@ -1,9 +1,9 @@
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { KeyGenerationService } from "@bitwarden/common/key-management/crypto";
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
import { PinServiceAbstraction } from "@bitwarden/common/key-management/pin/pin.service.abstraction";
import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";

View File

@@ -28,6 +28,7 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { getById } from "@bitwarden/common/platform/misc";
import { BannerModule, IconModule, AdminConsoleLogo } from "@bitwarden/components";
import { OrganizationWarningsModule } from "@bitwarden/web-vault/app/billing/organizations/warnings/organization-warnings.module";
import { OrganizationWarningsService } from "@bitwarden/web-vault/app/billing/organizations/warnings/services";
import { NonIndividualSubscriber } from "@bitwarden/web-vault/app/billing/types";
import { TaxIdWarningComponent } from "@bitwarden/web-vault/app/billing/warnings/components";
@@ -50,6 +51,7 @@ import { WebLayoutModule } from "../../../layouts/web-layout.module";
BannerModule,
TaxIdWarningComponent,
TaxIdWarningComponent,
OrganizationWarningsModule,
],
})
export class OrganizationLayoutComponent implements OnInit {

View File

@@ -34,6 +34,8 @@ import {
openChangePlanDialog,
} from "../../../billing/organizations/change-plan-dialog.component";
import { EventService } from "../../../core";
import { HeaderModule } from "../../../layouts/header/header.module";
import { SharedModule } from "../../../shared";
import { EventExportService } from "../../../tools/event-export";
import { BaseEventsComponent } from "../../common/base.events.component";
@@ -46,9 +48,8 @@ const EVENT_SYSTEM_USER_TO_TRANSLATION: Record<EventSystemUser, string> = {
};
@Component({
selector: "app-org-events",
templateUrl: "events.component.html",
standalone: false,
imports: [SharedModule, HeaderModule],
})
export class EventsComponent extends BaseEventsComponent implements OnInit, OnDestroy {
exportFileName = "org-events";

View File

@@ -8,6 +8,8 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
import { DIALOG_DATA, DialogConfig, DialogRef, DialogService } from "@bitwarden/components";
import { KeyService } from "@bitwarden/key-management";
import { SharedModule } from "../../../shared";
export type UserConfirmDialogData = {
name: string;
userId: string;
@@ -16,9 +18,8 @@ export type UserConfirmDialogData = {
};
@Component({
selector: "app-user-confirm",
templateUrl: "user-confirm.component.html",
standalone: false,
imports: [SharedModule],
})
export class UserConfirmComponent implements OnInit {
name: string;

View File

@@ -1,12 +1,14 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { CommonModule } from "@angular/common";
import { Component, inject } from "@angular/core";
import { Params } from "@angular/router";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
import { OrganizationSponsorshipResponse } from "@bitwarden/common/admin-console/models/response/organization-sponsorship.response";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { Icons, ToastService } from "@bitwarden/components";
import { IconModule, Icons, ToastService } from "@bitwarden/components";
import { I18nPipe } from "@bitwarden/ui-common";
import { BaseAcceptComponent } from "../../../common/base.accept.component";
@@ -16,9 +18,8 @@ import { BaseAcceptComponent } from "../../../common/base.accept.component";
* personal email address." - https://bitwarden.com/learning/free-families-plan-for-enterprise/
*/
@Component({
selector: "app-accept-family-sponsorship",
templateUrl: "accept-family-sponsorship.component.html",
standalone: false,
imports: [CommonModule, I18nPipe, IconModule],
})
export class AcceptFamilySponsorshipComponent extends BaseAcceptComponent {
protected logo = Icons.BitwardenLogo;

View File

@@ -8,10 +8,7 @@ import {
import { LayoutComponent, NavigationModule } from "@bitwarden/components";
import { OrganizationLayoutComponent } from "../admin-console/organizations/layouts/organization-layout.component";
import { EventsComponent as OrgEventsComponent } from "../admin-console/organizations/manage/events.component";
import { UserConfirmComponent as OrgUserConfirmComponent } from "../admin-console/organizations/manage/user-confirm.component";
import { VerifyRecoverDeleteOrgComponent } from "../admin-console/organizations/manage/verify-recover-delete-org.component";
import { AcceptFamilySponsorshipComponent } from "../admin-console/organizations/sponsorships/accept-family-sponsorship.component";
import { RecoverDeleteComponent } from "../auth/recover-delete.component";
import { RecoverTwoFactorComponent } from "../auth/recover-two-factor.component";
import { DangerZoneComponent } from "../auth/settings/account/danger-zone.component";
@@ -61,13 +58,10 @@ import { SharedModule } from "./shared.module";
PremiumBadgeComponent,
],
declarations: [
AcceptFamilySponsorshipComponent,
OrgEventsComponent,
OrgExposedPasswordsReportComponent,
OrgInactiveTwoFactorReportComponent,
OrgReusedPasswordsReportComponent,
OrgUnsecuredWebsitesReportComponent,
OrgUserConfirmComponent,
OrgWeakPasswordsReportComponent,
RecoverDeleteComponent,
RecoverTwoFactorComponent,
@@ -82,12 +76,10 @@ import { SharedModule } from "./shared.module";
UserVerificationModule,
PremiumBadgeComponent,
OrganizationLayoutComponent,
OrgEventsComponent,
OrgExposedPasswordsReportComponent,
OrgInactiveTwoFactorReportComponent,
OrgReusedPasswordsReportComponent,
OrgUnsecuredWebsitesReportComponent,
OrgUserConfirmComponent,
OrgWeakPasswordsReportComponent,
PremiumBadgeComponent,
RecoverDeleteComponent,

View File

@@ -6,10 +6,10 @@ import { filter, firstValueFrom, map, Subject, switchMap } from "rxjs";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { getUserId } from "@bitwarden/common/auth/services/account.service";
import { KeyGenerationService } from "@bitwarden/common/key-management/crypto";
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
import { ListResponse } from "@bitwarden/common/models/response/list.response";
import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { OrganizationId } from "@bitwarden/common/types/guid";

View File

@@ -12,11 +12,11 @@ import {
awaitAsync,
mockAccountServiceWith,
} from "../../../../spec";
import { KeyGenerationService } from "../../../key-management/crypto";
import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service";
import { EncString } from "../../../key-management/crypto/models/enc-string";
import { EnvironmentService } from "../../../platform/abstractions/environment.service";
import { I18nService } from "../../../platform/abstractions/i18n.service";
import { KeyGenerationService } from "../../../platform/abstractions/key-generation.service";
import { Utils } from "../../../platform/misc/utils";
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
import { ContainerService } from "../../../platform/services/container.service";

View File

@@ -6,10 +6,10 @@ import { Observable, concatMap, distinctUntilChanged, firstValueFrom, map } from
// eslint-disable-next-line no-restricted-imports
import { PBKDF2KdfConfig, KeyService } from "@bitwarden/key-management";
import { KeyGenerationService } from "../../../key-management/crypto";
import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service";
import { EncString } from "../../../key-management/crypto/models/enc-string";
import { I18nService } from "../../../platform/abstractions/i18n.service";
import { KeyGenerationService } from "../../../platform/abstractions/key-generation.service";
import { Utils } from "../../../platform/misc/utils";
import { EncArrayBuffer } from "../../../platform/models/domain/enc-array-buffer";
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";

View File

@@ -45,12 +45,14 @@ export class BufferedState<Input, Output, Dependency> implements SingleUserState
map((dependency) => [key.shouldOverwrite(dependency), dependency] as const),
);
const overwrite$ = combineLatest([hasValue$, overwriteDependency$]).pipe(
concatMap(async ([hasValue, [shouldOverwrite, dependency]]) => {
if (hasValue && shouldOverwrite) {
await this.overwriteOutput(dependency);
}
return [false, null] as const;
}),
concatMap(
async ([hasValue, [shouldOverwrite, dependency]]): Promise<readonly [false, null]> => {
if (hasValue && shouldOverwrite) {
await this.overwriteOutput(dependency);
}
return [false, null] as const;
},
),
);
// drive overwrites only when there's a subscription;
@@ -71,7 +73,7 @@ export class BufferedState<Input, Output, Dependency> implements SingleUserState
private async overwriteOutput(dependency: Dependency) {
// take the latest value from the buffer
let buffered: Input;
await this.bufferedState.update((state) => {
await this.bufferedState.update((state): Input | null => {
buffered = state ?? null;
return null;
});

View File

@@ -345,7 +345,8 @@ export class LegacyPasswordGenerationService implements PasswordGenerationServic
timeout({
// timeout after 1 second
each: 1000,
with() {
// TODO(PM-22309): Typescript 5.8 update, confirm type
with(): any[] {
return [];
},
}),
@@ -370,7 +371,8 @@ export class LegacyPasswordGenerationService implements PasswordGenerationServic
timeout({
// timeout after 1 second
each: 1000,
with() {
// TODO(PM-22309): Typescript 5.8 update, confirm type
with(): any[] {
return [];
},
}),