mirror of
https://github.com/bitwarden/browser
synced 2026-02-16 00:24:52 +00:00
Merge branch 'main' into dev/kreynolds/tunnel_proto_v2
This commit is contained in:
16
.github/workflows/build-desktop.yml
vendored
16
.github/workflows/build-desktop.yml
vendored
@@ -251,7 +251,7 @@ jobs:
|
||||
TARGET: musl
|
||||
run: |
|
||||
rustup target add x86_64-unknown-linux-musl
|
||||
node build.js --target=x86_64-unknown-linux-musl --release
|
||||
node build.js --target=x86_64-unknown-linux-musl
|
||||
|
||||
- name: Build application
|
||||
run: npm run dist:lin
|
||||
@@ -414,7 +414,7 @@ jobs:
|
||||
TARGET: musl
|
||||
run: |
|
||||
rustup target add aarch64-unknown-linux-musl
|
||||
node build.js --target=aarch64-unknown-linux-musl --release
|
||||
node build.js --target=aarch64-unknown-linux-musl
|
||||
|
||||
- name: Check index.d.ts generated
|
||||
if: github.event_name == 'pull_request' && steps.cache.outputs.cache-hit != 'true'
|
||||
@@ -995,12 +995,12 @@ jobs:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
node-version: ${{ env._NODE_VERSION }}
|
||||
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: '3.14'
|
||||
|
||||
|
||||
- name: Set up Node-gyp
|
||||
run: python3 -m pip install setuptools
|
||||
|
||||
@@ -1232,12 +1232,12 @@ jobs:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
node-version: ${{ env._NODE_VERSION }}
|
||||
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: '3.14'
|
||||
|
||||
|
||||
- name: Set up Node-gyp
|
||||
run: python3 -m pip install setuptools
|
||||
|
||||
@@ -1504,12 +1504,12 @@ jobs:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
node-version: ${{ env._NODE_VERSION }}
|
||||
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: '3.14'
|
||||
|
||||
|
||||
- name: Set up Node-gyp
|
||||
run: python3 -m pip install setuptools
|
||||
|
||||
|
||||
@@ -1530,5 +1530,63 @@ describe("NotificationBackground", () => {
|
||||
expect(environmentServiceSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("handleUnlockPopoutClosed", () => {
|
||||
let onRemovedListeners: Array<(tabId: number, removeInfo: chrome.tabs.OnRemovedInfo) => void>;
|
||||
let tabsQuerySpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
onRemovedListeners = [];
|
||||
chrome.tabs.onRemoved.addListener = jest.fn((listener) => {
|
||||
onRemovedListeners.push(listener);
|
||||
});
|
||||
chrome.runtime.getURL = jest.fn().mockReturnValue("chrome-extension://id/popup/index.html");
|
||||
notificationBackground.init();
|
||||
});
|
||||
|
||||
const triggerTabRemoved = async (tabId: number) => {
|
||||
onRemovedListeners[0](tabId, mock<chrome.tabs.OnRemovedInfo>());
|
||||
await flushPromises();
|
||||
};
|
||||
|
||||
it("sends abandon message when unlock popout is closed and vault is locked", async () => {
|
||||
activeAccountStatusMock$.next(AuthenticationStatus.Locked);
|
||||
tabsQuerySpy = jest.spyOn(BrowserApi, "tabsQuery").mockResolvedValue([]);
|
||||
|
||||
await triggerTabRemoved(1);
|
||||
|
||||
expect(tabsQuerySpy).toHaveBeenCalled();
|
||||
expect(messagingService.send).toHaveBeenCalledWith("abandonAutofillPendingNotifications");
|
||||
});
|
||||
|
||||
it("uses tracked tabId for fast lookup when available", async () => {
|
||||
activeAccountStatusMock$.next(AuthenticationStatus.Locked);
|
||||
tabsQuerySpy = jest.spyOn(BrowserApi, "tabsQuery").mockResolvedValue([
|
||||
{
|
||||
id: 123,
|
||||
url: "chrome-extension://id/popup/index.html?singleActionPopout=auth_unlockExtension",
|
||||
} as chrome.tabs.Tab,
|
||||
]);
|
||||
|
||||
await triggerTabRemoved(999);
|
||||
tabsQuerySpy.mockClear();
|
||||
messagingService.send.mockClear();
|
||||
|
||||
await triggerTabRemoved(123);
|
||||
|
||||
expect(tabsQuerySpy).not.toHaveBeenCalled();
|
||||
expect(messagingService.send).toHaveBeenCalledWith("abandonAutofillPendingNotifications");
|
||||
});
|
||||
|
||||
it("returns early when vault is unlocked", async () => {
|
||||
activeAccountStatusMock$.next(AuthenticationStatus.Unlocked);
|
||||
tabsQuerySpy = jest.spyOn(BrowserApi, "tabsQuery").mockResolvedValue([]);
|
||||
|
||||
await triggerTabRemoved(1);
|
||||
|
||||
expect(tabsQuerySpy).not.toHaveBeenCalled();
|
||||
expect(messagingService.send).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -45,7 +45,7 @@ import { SecurityTask } from "@bitwarden/common/vault/tasks/models/security-task
|
||||
|
||||
// FIXME (PM-22628): Popup imports are forbidden in background
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { openUnlockPopout } from "../../auth/popup/utils/auth-popout-window";
|
||||
import { AuthPopoutType, openUnlockPopout } from "../../auth/popup/utils/auth-popout-window";
|
||||
import { BrowserApi } from "../../platform/browser/browser-api";
|
||||
// FIXME (PM-22628): Popup imports are forbidden in background
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
@@ -89,6 +89,7 @@ export default class NotificationBackground {
|
||||
ExtensionCommand.AutofillCard,
|
||||
ExtensionCommand.AutofillIdentity,
|
||||
]);
|
||||
private unlockPopoutTabId?: number;
|
||||
private readonly extensionMessageHandlers: NotificationBackgroundExtensionMessageHandlers = {
|
||||
bgAdjustNotificationBar: ({ message, sender }) =>
|
||||
this.handleAdjustNotificationBarMessage(message, sender),
|
||||
@@ -146,6 +147,7 @@ export default class NotificationBackground {
|
||||
}
|
||||
|
||||
this.setupExtensionMessageListener();
|
||||
this.setupUnlockPopoutCloseListener();
|
||||
|
||||
this.cleanupNotificationQueue();
|
||||
}
|
||||
@@ -1163,6 +1165,7 @@ export default class NotificationBackground {
|
||||
message: NotificationBackgroundExtensionMessage,
|
||||
sender: chrome.runtime.MessageSender,
|
||||
): Promise<void> {
|
||||
this.unlockPopoutTabId = undefined;
|
||||
const messageData = message.data as LockedVaultPendingNotificationsData;
|
||||
const retryCommand = messageData.commandToRetry.message.command as ExtensionCommandType;
|
||||
if (this.allowedRetryCommands.has(retryCommand)) {
|
||||
@@ -1313,4 +1316,43 @@ export default class NotificationBackground {
|
||||
const tabDomain = Utils.getDomain(tab.url);
|
||||
return tabDomain === queueMessage.domain || tabDomain === Utils.getDomain(queueMessage.tab.url);
|
||||
}
|
||||
|
||||
private setupUnlockPopoutCloseListener() {
|
||||
chrome.tabs.onRemoved.addListener(async (tabId: number) => {
|
||||
await this.handleUnlockPopoutClosed(tabId);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* If the unlock popout is closed while the vault
|
||||
* is still locked and there are pending autofill notifications, abandon them.
|
||||
*/
|
||||
private async handleUnlockPopoutClosed(removedTabId: number) {
|
||||
const authStatus = await this.getAuthStatus();
|
||||
if (authStatus >= AuthenticationStatus.Unlocked) {
|
||||
this.unlockPopoutTabId = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.unlockPopoutTabId === removedTabId) {
|
||||
this.unlockPopoutTabId = undefined;
|
||||
this.messagingService.send("abandonAutofillPendingNotifications");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.unlockPopoutTabId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const extensionUrl = chrome.runtime.getURL("popup/index.html");
|
||||
const unlockPopoutTabs = (await BrowserApi.tabsQuery({ url: `${extensionUrl}*` })).filter(
|
||||
(tab) => tab.url?.includes(`singleActionPopout=${AuthPopoutType.unlockExtension}`),
|
||||
);
|
||||
|
||||
if (unlockPopoutTabs.length === 0) {
|
||||
this.messagingService.send("abandonAutofillPendingNotifications");
|
||||
} else if (unlockPopoutTabs[0].id) {
|
||||
this.unlockPopoutTabId = unlockPopoutTabs[0].id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -548,7 +548,7 @@ export default class MainBackground {
|
||||
this.memoryStorageForStateProviders = new BrowserMemoryStorageService(); // mv3 stores to storage.session
|
||||
this.memoryStorageService = this.memoryStorageForStateProviders;
|
||||
} else {
|
||||
this.memoryStorageForStateProviders = new BackgroundMemoryStorageService(); // mv2 stores to memory
|
||||
this.memoryStorageForStateProviders = new BackgroundMemoryStorageService(this.logService); // mv2 stores to memory
|
||||
this.memoryStorageService = this.memoryStorageForStateProviders;
|
||||
}
|
||||
|
||||
|
||||
@@ -256,6 +256,9 @@ export default class RuntimeBackground {
|
||||
case "addToLockedVaultPendingNotifications":
|
||||
this.lockedVaultPendingNotifications.push(msg.data);
|
||||
break;
|
||||
case "abandonAutofillPendingNotifications":
|
||||
this.lockedVaultPendingNotifications = [];
|
||||
break;
|
||||
case "lockVault":
|
||||
await this.lockService.lock(msg.userId);
|
||||
break;
|
||||
|
||||
@@ -60,8 +60,8 @@ export class BrowserApi {
|
||||
}
|
||||
|
||||
// Normalize both URLs by removing trailing slashes
|
||||
const normalizedOrigin = sender.origin.replace(/\/$/, "");
|
||||
const normalizedExtensionUrl = extensionUrl.replace(/\/$/, "");
|
||||
const normalizedOrigin = sender.origin.replace(/\/$/, "").toLowerCase();
|
||||
const normalizedExtensionUrl = extensionUrl.replace(/\/$/, "").toLowerCase();
|
||||
|
||||
if (!normalizedOrigin.startsWith(normalizedExtensionUrl)) {
|
||||
logger?.warning(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
|
||||
import { LogService } from "@bitwarden/logging";
|
||||
import { SerializedMemoryStorageService } from "@bitwarden/storage-core";
|
||||
|
||||
import { BrowserApi } from "../browser/browser-api";
|
||||
@@ -11,14 +12,14 @@ import { portName } from "./port-name";
|
||||
export class BackgroundMemoryStorageService extends SerializedMemoryStorageService {
|
||||
private _ports: chrome.runtime.Port[] = [];
|
||||
|
||||
constructor() {
|
||||
constructor(private readonly logService: LogService) {
|
||||
super();
|
||||
|
||||
BrowserApi.addListener(chrome.runtime.onConnect, (port) => {
|
||||
if (port.name !== portName(chrome.storage.session)) {
|
||||
return;
|
||||
}
|
||||
if (!BrowserApi.senderIsInternal(port.sender)) {
|
||||
if (!BrowserApi.senderIsInternal(port.sender, this.logService)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
*/
|
||||
|
||||
import { trackEmissions } from "@bitwarden/common/../spec/utils";
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
import { LogService } from "@bitwarden/logging";
|
||||
|
||||
import { mockPorts } from "../../../spec/mock-port.spec-util";
|
||||
|
||||
@@ -14,11 +17,13 @@ import { ForegroundMemoryStorageService } from "./foreground-memory-storage.serv
|
||||
describe.skip("foreground background memory storage interaction", () => {
|
||||
let foreground: ForegroundMemoryStorageService;
|
||||
let background: BackgroundMemoryStorageService;
|
||||
let logService: MockProxy<LogService>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockPorts();
|
||||
logService = mock();
|
||||
|
||||
background = new BackgroundMemoryStorageService();
|
||||
background = new BackgroundMemoryStorageService(logService);
|
||||
foreground = new ForegroundMemoryStorageService();
|
||||
});
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<button bitButton type="submit" form="sendForm" buttonType="primary" #submitBtn>
|
||||
{{ "save" | i18n }}
|
||||
</button>
|
||||
<button bitButton type="button" buttonType="secondary" popupBackAction>
|
||||
<button bitButton type="button" buttonType="secondary" [popupBackAction]>
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
<button
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<popup-page [loading]="sendsLoading$ | async">
|
||||
<popup-page [loading]="showSpinnerLoaders$ | async" [hideOverflow]="showSkeletonsLoaders$ | async">
|
||||
<popup-header slot="header" [pageTitle]="'send' | i18n">
|
||||
<ng-container slot="end">
|
||||
<tools-new-send-dropdown *ngIf="!sendsDisabled"></tools-new-send-dropdown>
|
||||
@@ -6,7 +6,7 @@
|
||||
<app-current-account></app-current-account>
|
||||
</ng-container>
|
||||
</popup-header>
|
||||
<ng-container slot="above-scroll-area" *ngIf="!(sendsLoading$ | async)">
|
||||
<ng-container slot="above-scroll-area">
|
||||
<bit-callout *ngIf="sendsDisabled" [title]="'sendDisabled' | i18n">
|
||||
{{ "sendDisabledWarning" | i18n }}
|
||||
</bit-callout>
|
||||
@@ -34,7 +34,7 @@
|
||||
</bit-no-items>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="listState !== sendState.Empty">
|
||||
<ng-container *ngIf="listState !== sendState.Empty && !(showSkeletonsLoaders$ | async)">
|
||||
<div
|
||||
*ngIf="listState === sendState.NoResults"
|
||||
class="tw-flex tw-flex-col tw-justify-center tw-h-auto tw-pt-12"
|
||||
@@ -46,4 +46,9 @@
|
||||
</div>
|
||||
<app-send-list-items-container [headerText]="title | i18n" [sends]="sends$ | async" />
|
||||
</ng-container>
|
||||
@if (showSkeletonsLoaders$ | async) {
|
||||
<vault-fade-in-skeleton>
|
||||
<vault-loading-skeleton></vault-loading-skeleton>
|
||||
</vault-fade-in-skeleton>
|
||||
}
|
||||
</popup-page>
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, OnDestroy } from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { combineLatest, switchMap } from "rxjs";
|
||||
import { combineLatest, distinctUntilChanged, map, shareReplay, switchMap } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { NoResults, NoSendsIcon } from "@bitwarden/assets/svg";
|
||||
import { VaultLoadingSkeletonComponent } from "@bitwarden/browser/vault/popup/components/vault-loading-skeleton/vault-loading-skeleton.component";
|
||||
import { BrowserPremiumUpgradePromptService } from "@bitwarden/browser/vault/popup/services/browser-premium-upgrade-prompt.service";
|
||||
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { SendType } from "@bitwarden/common/tools/send/enums/send-type";
|
||||
import { PremiumUpgradePromptService } from "@bitwarden/common/vault/abstractions/premium-upgrade-prompt.service";
|
||||
import {
|
||||
@@ -31,6 +34,7 @@ import { CurrentAccountComponent } from "../../../auth/popup/account-switching/c
|
||||
import { PopOutComponent } from "../../../platform/popup/components/pop-out.component";
|
||||
import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component";
|
||||
import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component";
|
||||
import { VaultFadeInOutSkeletonComponent } from "../../../vault/popup/components/vault-fade-in-out-skeleton/vault-fade-in-out-skeleton.component";
|
||||
|
||||
// FIXME: update to use a const object instead of a typescript enum
|
||||
// eslint-disable-next-line @bitwarden/platform/no-enums
|
||||
@@ -64,6 +68,8 @@ export enum SendState {
|
||||
SendListFiltersComponent,
|
||||
SendSearchComponent,
|
||||
TypographyModule,
|
||||
VaultFadeInOutSkeletonComponent,
|
||||
VaultLoadingSkeletonComponent,
|
||||
],
|
||||
})
|
||||
export class SendV2Component implements OnDestroy {
|
||||
@@ -72,7 +78,26 @@ export class SendV2Component implements OnDestroy {
|
||||
|
||||
protected listState: SendState | null = null;
|
||||
protected sends$ = this.sendItemsService.filteredAndSortedSends$;
|
||||
protected sendsLoading$ = this.sendItemsService.loading$;
|
||||
private skeletonFeatureFlag$ = this.configService.getFeatureFlag$(
|
||||
FeatureFlag.VaultLoadingSkeletons,
|
||||
);
|
||||
protected sendsLoading$ = this.sendItemsService.loading$.pipe(
|
||||
distinctUntilChanged(),
|
||||
shareReplay({ bufferSize: 1, refCount: true }),
|
||||
);
|
||||
|
||||
/** Spinner Loading State */
|
||||
protected showSpinnerLoaders$ = combineLatest([
|
||||
this.sendsLoading$,
|
||||
this.skeletonFeatureFlag$,
|
||||
]).pipe(map(([loading, skeletonsEnabled]) => loading && !skeletonsEnabled));
|
||||
|
||||
/** Skeleton Loading State */
|
||||
protected showSkeletonsLoaders$ = combineLatest([
|
||||
this.sendsLoading$,
|
||||
this.skeletonFeatureFlag$,
|
||||
]).pipe(map(([loading, skeletonsEnabled]) => loading && skeletonsEnabled));
|
||||
|
||||
protected title: string = "allSends";
|
||||
protected noItemIcon = NoSendsIcon;
|
||||
protected noResultsIcon = NoResults;
|
||||
@@ -84,6 +109,7 @@ export class SendV2Component implements OnDestroy {
|
||||
protected sendListFiltersService: SendListFiltersService,
|
||||
private policyService: PolicyService,
|
||||
private accountService: AccountService,
|
||||
private configService: ConfigService,
|
||||
) {
|
||||
combineLatest([
|
||||
this.sendItemsService.emptyList$,
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
>
|
||||
{{ "exportVault" | i18n }}
|
||||
</button>
|
||||
<button bitButton type="button" buttonType="secondary" popupBackAction>
|
||||
<button bitButton type="button" buttonType="secondary" [popupBackAction]>
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
</popup-footer>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"version": "0.1.0",
|
||||
"description": "",
|
||||
"scripts": {
|
||||
"build": "napi build --platform --js false",
|
||||
"build": "node scripts/build.js",
|
||||
"test": "cargo test"
|
||||
},
|
||||
"author": "",
|
||||
|
||||
14
apps/desktop/desktop_native/napi/scripts/build.js
Normal file
14
apps/desktop/desktop_native/napi/scripts/build.js
Normal file
@@ -0,0 +1,14 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const isRelease = args.includes('--release');
|
||||
|
||||
if (isRelease) {
|
||||
console.log('Building release mode.');
|
||||
} else {
|
||||
console.log('Building debug mode.');
|
||||
process.env.RUST_LOG = 'debug';
|
||||
}
|
||||
|
||||
execSync(`napi build --platform --js false`, { stdio: 'inherit', env: process.env });
|
||||
@@ -957,10 +957,7 @@ pub mod logging {
|
||||
use tracing::Level;
|
||||
use tracing_subscriber::fmt::format::{DefaultVisitor, Writer};
|
||||
use tracing_subscriber::{
|
||||
filter::{EnvFilter, LevelFilter},
|
||||
layer::SubscriberExt,
|
||||
util::SubscriberInitExt,
|
||||
Layer,
|
||||
filter::EnvFilter, layer::SubscriberExt, util::SubscriberInitExt, Layer,
|
||||
};
|
||||
|
||||
struct JsLogger(OnceLock<ThreadsafeFunction<(LogLevel, String), CalleeHandled>>);
|
||||
@@ -1044,9 +1041,17 @@ pub mod logging {
|
||||
pub fn init_napi_log(js_log_fn: ThreadsafeFunction<(LogLevel, String), CalleeHandled>) {
|
||||
let _ = JS_LOGGER.0.set(js_log_fn);
|
||||
|
||||
// the log level hierarchy is determined by:
|
||||
// - if RUST_LOG is detected at runtime
|
||||
// - if RUST_LOG is provided at compile time
|
||||
// - default to INFO
|
||||
let filter = EnvFilter::builder()
|
||||
// set the default log level to INFO.
|
||||
.with_default_directive(LevelFilter::INFO.into())
|
||||
.with_default_directive(
|
||||
option_env!("RUST_LOG")
|
||||
.unwrap_or("info")
|
||||
.parse()
|
||||
.expect("should provide valid log level at compile time."),
|
||||
)
|
||||
// parse directives from the RUST_LOG environment variable,
|
||||
// overriding the default directive for matching targets.
|
||||
.from_env_lossy();
|
||||
|
||||
@@ -93,8 +93,8 @@
|
||||
"assignMembersTasksToMonitorProgress": {
|
||||
"message": "Assign members tasks to monitor progress"
|
||||
},
|
||||
"onceYouReviewApps": {
|
||||
"message": "Once you review applications and mark them as critical, you can assign tasks to members to resolve at-risk items and monitor progress here"
|
||||
"onceYouReviewApplications": {
|
||||
"message": "Once you review applications and mark them as critical, assign tasks to your members to change their passwords."
|
||||
},
|
||||
"sendReminders": {
|
||||
"message": "Send reminders"
|
||||
@@ -178,41 +178,35 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"noApplicationsInOrgTitle": {
|
||||
"message": "No applications found for $ORG NAME$",
|
||||
"placeholders": {
|
||||
"org name": {
|
||||
"content": "$1",
|
||||
"example": "Company Name"
|
||||
}
|
||||
}
|
||||
"noDataInOrgTitle": {
|
||||
"message": "No data found"
|
||||
},
|
||||
"noApplicationsInOrgDescription": {
|
||||
"message": "Import your organization's login data to start monitoring credential security risks. Once imported you get to:"
|
||||
"noDataInOrgDescription": {
|
||||
"message": "Import your organization's login data to get started with Access Intelligence. Once you do that, you'll be able to:"
|
||||
},
|
||||
"benefit1Title": {
|
||||
"message": "Prioritize risks"
|
||||
"feature1Title": {
|
||||
"message": "Mark applications as critical"
|
||||
},
|
||||
"benefit1Description": {
|
||||
"message": "Focus on applications that matter the most"
|
||||
"feature1Description": {
|
||||
"message": "This will help you remove risks to your most important applications first."
|
||||
},
|
||||
"benefit2Title": {
|
||||
"message": "Guide remediation"
|
||||
"feature2Title": {
|
||||
"message": "Help members improve their security"
|
||||
},
|
||||
"benefit2Description": {
|
||||
"message": "Assign at-risk members guided tasks to rotate at-risk credentials"
|
||||
"feature2Description": {
|
||||
"message": "Assign at-risk members guided security tasks to update credentials."
|
||||
},
|
||||
"benefit3Title": {
|
||||
"feature3Title": {
|
||||
"message": "Monitor progress"
|
||||
},
|
||||
"benefit3Description": {
|
||||
"message": "Track changes over time to show security improvements"
|
||||
"feature3Description": {
|
||||
"message": "Track changes over time to show security improvements."
|
||||
},
|
||||
"noReportRunTitle": {
|
||||
"message": "Run your first report to see applications"
|
||||
"noReportsRunTitle": {
|
||||
"message": "Generate report"
|
||||
},
|
||||
"noReportRunDescription": {
|
||||
"message": "Generate a risk insights report to analyze your organization's applications and identify at-risk passwords that need attention. Running your first report will:"
|
||||
"noReportsRunDescription": {
|
||||
"message": "You’re ready to start generating reports. Once you generate, you’ll be able to:"
|
||||
},
|
||||
"noCriticalApplicationsTitle": {
|
||||
"message": "You haven’t marked any applications as critical"
|
||||
@@ -271,14 +265,14 @@
|
||||
"atRiskMembers": {
|
||||
"message": "At-risk members"
|
||||
},
|
||||
"membersWithAccessToAtRiskItemsForCriticalApps": {
|
||||
"message": "Members with access to at-risk items for critical applications"
|
||||
"membersWithAccessToAtRiskItemsForCriticalApplications": {
|
||||
"message": "These members have access to vulnerable items for critical applications."
|
||||
},
|
||||
"membersWithAtRiskPasswords": {
|
||||
"message": "Members with at-risk passwords"
|
||||
},
|
||||
"membersWillReceiveNotification": {
|
||||
"message": "Members will receive a notification to resolve at-risk logins through the browser extension."
|
||||
"membersWillReceiveSecurityTask": {
|
||||
"message": "Members of your organization will be assigned a task to change vulnerable passwords. They’ll receive a notification within their Bitwarden browser extension."
|
||||
},
|
||||
"membersAtRiskCount": {
|
||||
"message": "$COUNT$ members at-risk",
|
||||
@@ -307,8 +301,8 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"atRiskMembersDescription": {
|
||||
"message": "These members are logging into applications with weak, exposed, or reused passwords."
|
||||
"atRiskMemberDescription": {
|
||||
"message": "These members are logging into critical applications with weak, exposed, or reused passwords."
|
||||
},
|
||||
"atRiskMembersDescriptionNone": {
|
||||
"message": "These are no members logging into applications with weak, exposed, or reused passwords."
|
||||
@@ -391,14 +385,14 @@
|
||||
"prioritizeCriticalApplications": {
|
||||
"message": "Prioritize critical applications"
|
||||
},
|
||||
"selectCriticalApplicationsDescription": {
|
||||
"message": "Select which applications are most critical to your organization, then assign security tasks to members to resolve risks."
|
||||
"selectCriticalAppsDescription": {
|
||||
"message": "Select which applications are most critical to your organization. Then, you’ll be able to assign security tasks to members to remove risks."
|
||||
},
|
||||
"reviewNewApplications": {
|
||||
"message": "Review new applications"
|
||||
},
|
||||
"reviewNewApplicationsDescription": {
|
||||
"message": "We've highlighted at-risk items for new applications stored in Admin console that have weak, exposed, or reused passwords."
|
||||
"reviewNewAppsDescription": {
|
||||
"message": "Review new applications with vulnerable items and mark those you’d like to monitor closely as critical. Then, you’ll be able to assign security tasks to members to remove risks."
|
||||
},
|
||||
"clickIconToMarkAppAsCritical": {
|
||||
"message": "Click the star icon to mark an app as critical"
|
||||
@@ -9860,8 +9854,8 @@
|
||||
"assignTasks": {
|
||||
"message": "Assign tasks"
|
||||
},
|
||||
"assignTasksToMembers": {
|
||||
"message": "Assign tasks to members for guided resolution"
|
||||
"assignSecurityTasksToMembers": {
|
||||
"message": "Send notifications to change passwords"
|
||||
},
|
||||
"assignToCollections": {
|
||||
"message": "Assign to collections"
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</div>
|
||||
|
||||
<div class="tw-items-baseline tw-gap-2">
|
||||
<span bitTypography="body2">{{ "onceYouReviewApps" | i18n }}</span>
|
||||
<span bitTypography="body2">{{ "onceYouReviewApplications" | i18n }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<dirt-activity-card
|
||||
[title]="'atRiskMembers' | i18n"
|
||||
[cardMetrics]="'membersAtRiskCount' | i18n: totalCriticalAppsAtRiskMemberCount"
|
||||
[metricDescription]="'membersWithAccessToAtRiskItemsForCriticalApps' | i18n"
|
||||
[metricDescription]="'membersWithAccessToAtRiskItemsForCriticalApplications' | i18n"
|
||||
actionText="{{ 'viewAtRiskMembers' | i18n }}"
|
||||
[showActionLink]="totalCriticalAppsAtRiskMemberCount > 0"
|
||||
(actionClick)="onViewAtRiskMembers()"
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
|
||||
<!-- Description Text -->
|
||||
<div bitTypography="helper" class="tw-text-muted">
|
||||
{{ "membersWillReceiveNotification" | i18n }}
|
||||
{{ "membersWillReceiveSecurityTask" | i18n }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
? hasNoCriticalApplications()
|
||||
? ("prioritizeCriticalApplications" | i18n)
|
||||
: ("reviewNewApplications" | i18n)
|
||||
: ("assignTasksToMembers" | i18n)
|
||||
: ("assignSecurityTasksToMembers" | i18n)
|
||||
}}
|
||||
</span>
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
<p bitTypography="body1" class="tw-mb-5">
|
||||
{{
|
||||
hasNoCriticalApplications()
|
||||
? ("selectCriticalApplicationsDescription" | i18n)
|
||||
: ("reviewNewApplicationsDescription" | i18n)
|
||||
? ("selectCriticalAppsDescription" | i18n)
|
||||
: ("reviewNewAppsDescription" | i18n)
|
||||
}}
|
||||
</p>
|
||||
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
<!-- Show Empty state when there are no applications (no ciphers to make reports on) -->
|
||||
<empty-state-card
|
||||
[videoSrc]="emptyStateVideoSrc"
|
||||
[title]="this.i18nService.t('noApplicationsInOrgTitle', organizationName)"
|
||||
[description]="this.i18nService.t('noApplicationsInOrgDescription')"
|
||||
[title]="this.i18nService.t('noDataInOrgTitle')"
|
||||
[description]="this.i18nService.t('noDataInOrgDescription')"
|
||||
[benefits]="emptyStateBenefits"
|
||||
[buttonText]="this.i18nService.t('importData')"
|
||||
[buttonIcon]="IMPORT_ICON"
|
||||
@@ -27,8 +27,8 @@
|
||||
<!-- Show empty state for no reports run -->
|
||||
<empty-state-card
|
||||
[videoSrc]="emptyStateVideoSrc"
|
||||
[title]="this.i18nService.t('noReportRunTitle')"
|
||||
[description]="this.i18nService.t('noReportRunDescription')"
|
||||
[title]="this.i18nService.t('noReportsRunTitle')"
|
||||
[description]="this.i18nService.t('noReportsRunDescription')"
|
||||
[benefits]="emptyStateBenefits"
|
||||
[buttonText]="this.i18nService.t('riskInsightsRunReport')"
|
||||
[buttonIcon]=""
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from "@angular/core";
|
||||
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { combineLatest, EMPTY, firstValueFrom } from "rxjs";
|
||||
import { EMPTY, firstValueFrom } from "rxjs";
|
||||
import { distinctUntilChanged, map, tap } from "rxjs/operators";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
@@ -84,14 +84,11 @@ export class RiskInsightsComponent implements OnInit, OnDestroy {
|
||||
|
||||
dataLastUpdated: Date | null = null;
|
||||
|
||||
// Empty state properties
|
||||
protected organizationName = "";
|
||||
|
||||
// Empty state computed properties
|
||||
protected emptyStateBenefits: [string, string][] = [
|
||||
[this.i18nService.t("benefit1Title"), this.i18nService.t("benefit1Description")],
|
||||
[this.i18nService.t("benefit2Title"), this.i18nService.t("benefit2Description")],
|
||||
[this.i18nService.t("benefit3Title"), this.i18nService.t("benefit3Description")],
|
||||
[this.i18nService.t("feature1Title"), this.i18nService.t("feature1Description")],
|
||||
[this.i18nService.t("feature2Title"), this.i18nService.t("feature2Description")],
|
||||
[this.i18nService.t("feature3Title"), this.i18nService.t("feature3Description")],
|
||||
];
|
||||
protected emptyStateVideoSrc: string | null = "/videos/risk-insights-mark-as-critical.mp4";
|
||||
|
||||
@@ -140,17 +137,14 @@ export class RiskInsightsComponent implements OnInit, OnDestroy {
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
// Combine report data, vault items check, organization details, and generation state
|
||||
// Subscribe to report data updates
|
||||
// This declarative pattern ensures proper cleanup and prevents memory leaks
|
||||
combineLatest([this.dataService.enrichedReportData$, this.dataService.organizationDetails$])
|
||||
this.dataService.enrichedReportData$
|
||||
.pipe(takeUntilDestroyed(this.destroyRef))
|
||||
.subscribe(([report, orgDetails]) => {
|
||||
.subscribe((report) => {
|
||||
// Update report state
|
||||
this.appsCount = report?.reportData.length ?? 0;
|
||||
this.dataLastUpdated = report?.creationDate ?? null;
|
||||
|
||||
// Update organization name
|
||||
this.organizationName = orgDetails?.organizationName ?? "";
|
||||
});
|
||||
|
||||
// Subscribe to drawer state changes
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<ng-container bitDialogContent>
|
||||
<span bitTypography="body1" class="tw-text-muted tw-text-sm">{{
|
||||
(drawerDetails.atRiskMemberDetails?.length > 0
|
||||
? "atRiskMembersDescription"
|
||||
? "atRiskMemberDescription"
|
||||
: "atRiskMembersDescriptionNone"
|
||||
) | i18n
|
||||
}}</span>
|
||||
|
||||
@@ -13,7 +13,7 @@ export abstract class SendTokenService {
|
||||
/**
|
||||
* Attempts to retrieve a {@link SendAccessToken} for the given sendId.
|
||||
* If the access token is found in session storage and is not expired, then it returns the token.
|
||||
* If the access token is expired, then it returns a {@link TryGetSendAccessTokenError} expired error.
|
||||
* If the access token found in session storage is expired, then it returns a {@link TryGetSendAccessTokenError} expired error and clears the token from storage so that a subsequent call can attempt to retrieve a new token.
|
||||
* If an access token is not found in storage, then it attempts to retrieve it from the server (will succeed for sends that don't require any credentials to view).
|
||||
* If the access token is successfully retrieved from the server, then it stores the token in session storage and returns it.
|
||||
* If an access token cannot be granted b/c the send requires credentials, then it returns a {@link TryGetSendAccessTokenError} indicating which credentials are required.
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
<bit-callout type="info" *ngIf="importBlockedByPolicy">
|
||||
{{ "personalOwnershipPolicyInEffectImports" | i18n }}
|
||||
</bit-callout>
|
||||
<bit-callout
|
||||
[title]="'restrictCardTypeImport' | i18n"
|
||||
type="info"
|
||||
|
||||
Reference in New Issue
Block a user