mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 05:13:29 +00:00
Merge branch 'main' into PM-26250-Explore-options-to-enable-direct-importer-for-mac-app-store-build
This commit is contained in:
@@ -4902,6 +4902,9 @@
|
||||
"premium": {
|
||||
"message": "Premium"
|
||||
},
|
||||
"unlockFeaturesWithPremium": {
|
||||
"message": "Unlock reporting, emergency access, and more security features with Premium."
|
||||
},
|
||||
"freeOrgsCannotUseAttachments": {
|
||||
"message": "Free organizations cannot use attachments"
|
||||
},
|
||||
|
||||
@@ -726,17 +726,6 @@ export default class MainBackground {
|
||||
|
||||
const pinStateService = new PinStateService(this.stateProvider);
|
||||
|
||||
this.pinService = new PinService(
|
||||
this.accountService,
|
||||
this.encryptService,
|
||||
this.kdfConfigService,
|
||||
this.keyGenerationService,
|
||||
this.logService,
|
||||
this.keyService,
|
||||
this.sdkService,
|
||||
pinStateService,
|
||||
);
|
||||
|
||||
this.appIdService = new AppIdService(this.storageService, this.logService);
|
||||
|
||||
this.userDecryptionOptionsService = new UserDecryptionOptionsService(this.stateProvider);
|
||||
@@ -756,16 +745,6 @@ export default class MainBackground {
|
||||
VaultTimeoutStringType.OnRestart, // default vault timeout
|
||||
);
|
||||
|
||||
this.biometricsService = new BackgroundBrowserBiometricsService(
|
||||
runtimeNativeMessagingBackground,
|
||||
this.logService,
|
||||
this.keyService,
|
||||
this.biometricStateService,
|
||||
this.messagingService,
|
||||
this.vaultTimeoutSettingsService,
|
||||
this.pinService,
|
||||
);
|
||||
|
||||
this.apiService = new ApiService(
|
||||
this.tokenService,
|
||||
this.platformUtilsService,
|
||||
@@ -849,6 +828,27 @@ export default class MainBackground {
|
||||
this.configService,
|
||||
);
|
||||
|
||||
this.pinService = new PinService(
|
||||
this.accountService,
|
||||
this.encryptService,
|
||||
this.kdfConfigService,
|
||||
this.keyGenerationService,
|
||||
this.logService,
|
||||
this.keyService,
|
||||
this.sdkService,
|
||||
pinStateService,
|
||||
);
|
||||
|
||||
this.biometricsService = new BackgroundBrowserBiometricsService(
|
||||
runtimeNativeMessagingBackground,
|
||||
this.logService,
|
||||
this.keyService,
|
||||
this.biometricStateService,
|
||||
this.messagingService,
|
||||
this.vaultTimeoutSettingsService,
|
||||
this.pinService,
|
||||
);
|
||||
|
||||
this.passwordStrengthService = new PasswordStrengthService();
|
||||
|
||||
this.passwordGenerationService = legacyPasswordGenerationServiceFactory(
|
||||
|
||||
@@ -293,14 +293,24 @@ export default class RuntimeBackground {
|
||||
case "openPopup":
|
||||
await this.openPopup();
|
||||
break;
|
||||
case VaultMessages.OpenAtRiskPasswords:
|
||||
case VaultMessages.OpenAtRiskPasswords: {
|
||||
if (await this.shouldRejectManyOriginMessage(msg)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.main.openAtRisksPasswordsPage();
|
||||
this.announcePopupOpen();
|
||||
break;
|
||||
case VaultMessages.OpenBrowserExtensionToUrl:
|
||||
}
|
||||
case VaultMessages.OpenBrowserExtensionToUrl: {
|
||||
if (await this.shouldRejectManyOriginMessage(msg)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.main.openTheExtensionToPage(msg.url);
|
||||
this.announcePopupOpen();
|
||||
break;
|
||||
}
|
||||
case "bgUpdateContextMenu":
|
||||
case "editedCipher":
|
||||
case "addedCipher":
|
||||
@@ -312,10 +322,7 @@ export default class RuntimeBackground {
|
||||
break;
|
||||
}
|
||||
case "authResult": {
|
||||
const env = await firstValueFrom(this.environmentService.environment$);
|
||||
const vaultUrl = env.getWebVaultUrl();
|
||||
|
||||
if (msg.referrer == null || Utils.getHostname(vaultUrl) !== msg.referrer) {
|
||||
if (!(await this.isValidVaultReferrer(msg.referrer))) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -334,10 +341,7 @@ export default class RuntimeBackground {
|
||||
break;
|
||||
}
|
||||
case "webAuthnResult": {
|
||||
const env = await firstValueFrom(this.environmentService.environment$);
|
||||
const vaultUrl = env.getWebVaultUrl();
|
||||
|
||||
if (msg.referrer == null || Utils.getHostname(vaultUrl) !== msg.referrer) {
|
||||
if (!(await this.isValidVaultReferrer(msg.referrer))) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -372,6 +376,48 @@ export default class RuntimeBackground {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For messages that can originate from a vault host page or extension, validate referrer or external
|
||||
*
|
||||
* @param message
|
||||
* @returns true if message fails validation
|
||||
*/
|
||||
private async shouldRejectManyOriginMessage(message: {
|
||||
webExtSender: chrome.runtime.MessageSender;
|
||||
}): Promise<boolean> {
|
||||
const isValidVaultReferrer = await this.isValidVaultReferrer(
|
||||
Utils.getHostname(message?.webExtSender?.origin),
|
||||
);
|
||||
|
||||
if (isValidVaultReferrer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isExternalMessage(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a message's referrer matches the configured web vault hostname.
|
||||
*
|
||||
* @param referrer - hostname from message source
|
||||
* @returns true if referrer matches web vault
|
||||
*/
|
||||
private async isValidVaultReferrer(referrer: string | null | undefined): Promise<boolean> {
|
||||
if (!referrer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const env = await firstValueFrom(this.environmentService.environment$);
|
||||
const vaultUrl = env.getWebVaultUrl();
|
||||
const vaultHostname = Utils.getHostname(vaultUrl);
|
||||
|
||||
if (!vaultHostname) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return vaultHostname === referrer;
|
||||
}
|
||||
|
||||
private async autofillPage(tabToAutoFill: chrome.tabs.Tab) {
|
||||
const totpCode = await this.autofillService.doAutoFill({
|
||||
tab: tabToAutoFill,
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { CommonModule } from "@angular/common";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Component, inject } from "@angular/core";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { ActivatedRoute, RouterModule } from "@angular/router";
|
||||
import { firstValueFrom, map } from "rxjs";
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// TODO: This needs to be dealt with by moving this folder or updating the lint rule.
|
||||
/* eslint-disable no-restricted-imports */
|
||||
import { ActivatedRoute, RouterModule } from "@angular/router";
|
||||
import { Meta, StoryObj, moduleMetadata } from "@storybook/angular";
|
||||
import { BehaviorSubject, of } from "rxjs";
|
||||
@@ -1,6 +1,4 @@
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { CommonModule } from "@angular/common";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
@@ -56,8 +56,8 @@ import { BlockedDomainsComponent } from "../autofill/popup/settings/blocked-doma
|
||||
import { ExcludedDomainsComponent } from "../autofill/popup/settings/excluded-domains.component";
|
||||
import { NotificationsSettingsComponent } from "../autofill/popup/settings/notifications.component";
|
||||
import { PremiumV2Component } from "../billing/popup/settings/premium-v2.component";
|
||||
import { PhishingWarning } from "../dirt/phishing-detection/pages/phishing-warning.component";
|
||||
import { ProtectedByComponent } from "../dirt/phishing-detection/pages/protected-by-component";
|
||||
import { PhishingWarning } from "../dirt/phishing-detection/popup/phishing-warning.component";
|
||||
import { ProtectedByComponent } from "../dirt/phishing-detection/popup/protected-by-component";
|
||||
import { RemovePasswordComponent } from "../key-management/key-connector/remove-password.component";
|
||||
import BrowserPopupUtils from "../platform/browser/browser-popup-utils";
|
||||
import { popupRouterCacheGuard } from "../platform/popup/view-cache/popup-router-cache.service";
|
||||
|
||||
@@ -1,4 +1,19 @@
|
||||
<popup-page>
|
||||
<bit-spotlight *ngIf="!(hasPremium$ | async)" persistent>
|
||||
<span class="tw-text-xs"
|
||||
>{{ "unlockFeaturesWithPremium" | i18n }}
|
||||
<button
|
||||
bitLink
|
||||
buttonType="primary"
|
||||
class="tw-text-xs"
|
||||
type="button"
|
||||
(click)="openUpgradeDialog()"
|
||||
[title]="'upgradeNow' | i18n"
|
||||
>
|
||||
{{ "upgradeNow" | i18n }}
|
||||
</button>
|
||||
</span>
|
||||
</bit-spotlight>
|
||||
<popup-header slot="header" pageTitle="{{ 'settings' | i18n }}">
|
||||
<ng-container slot="end">
|
||||
<app-pop-out></app-pop-out>
|
||||
@@ -20,7 +35,7 @@
|
||||
<div class="tw-flex tw-items-center tw-justify-center">
|
||||
<p class="tw-pr-2">{{ "autofill" | i18n }}</p>
|
||||
<span
|
||||
*ngIf="!isBrowserAutofillSettingOverridden && (showAutofillBadge$ | async)"
|
||||
*ngIf="!(isBrowserAutofillSettingOverridden$ | async) && (showAutofillBadge$ | async)"
|
||||
bitBadge
|
||||
variant="notification"
|
||||
[attr.aria-label]="'nudgeBadgeAria' | i18n"
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
import { ChangeDetectionStrategy, Component, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
|
||||
import { TestBed, waitForAsync } from "@angular/core/testing";
|
||||
import { RouterTestingModule } from "@angular/router/testing";
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
import { BehaviorSubject, firstValueFrom, of, Subject } from "rxjs";
|
||||
|
||||
import { PremiumUpgradeDialogComponent } from "@bitwarden/angular/billing/components";
|
||||
import { NudgesService, NudgeType } from "@bitwarden/angular/vault";
|
||||
import { AutofillBrowserSettingsService } from "@bitwarden/browser/autofill/services/autofill-browser-settings.service";
|
||||
import { BrowserApi } from "@bitwarden/browser/platform/browser/browser-api";
|
||||
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { GlobalStateProvider } from "@bitwarden/state";
|
||||
import { FakeGlobalStateProvider } from "@bitwarden/state-test-utils";
|
||||
|
||||
import { CurrentAccountComponent } from "../../../auth/popup/account-switching/current-account.component";
|
||||
|
||||
import { SettingsV2Component } from "./settings-v2.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-current-account",
|
||||
standalone: true,
|
||||
template: "",
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
class CurrentAccountStubComponent {}
|
||||
|
||||
describe("SettingsV2Component", () => {
|
||||
let account$: BehaviorSubject<Account | null>;
|
||||
let mockAccountService: Partial<AccountService>;
|
||||
let mockBillingState: { hasPremiumFromAnySource$: jest.Mock };
|
||||
let mockNudges: {
|
||||
showNudgeBadge$: jest.Mock;
|
||||
dismissNudge: jest.Mock;
|
||||
};
|
||||
let mockAutofillSettings: {
|
||||
defaultBrowserAutofillDisabled$: Subject<boolean>;
|
||||
isBrowserAutofillSettingOverridden: jest.Mock<Promise<boolean>>;
|
||||
};
|
||||
let dialogService: MockProxy<DialogService>;
|
||||
let openSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(waitForAsync(async () => {
|
||||
dialogService = mock<DialogService>();
|
||||
account$ = new BehaviorSubject<Account | null>(null);
|
||||
mockAccountService = {
|
||||
activeAccount$: account$ as unknown as AccountService["activeAccount$"],
|
||||
};
|
||||
|
||||
mockBillingState = {
|
||||
hasPremiumFromAnySource$: jest.fn().mockReturnValue(of(false)),
|
||||
};
|
||||
|
||||
mockNudges = {
|
||||
showNudgeBadge$: jest.fn().mockImplementation(() => of(false)),
|
||||
dismissNudge: jest.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
|
||||
mockAutofillSettings = {
|
||||
defaultBrowserAutofillDisabled$: new BehaviorSubject<boolean>(false),
|
||||
isBrowserAutofillSettingOverridden: jest.fn().mockResolvedValue(false),
|
||||
};
|
||||
|
||||
jest.spyOn(BrowserApi, "getBrowserClientVendor").mockReturnValue("Chrome");
|
||||
|
||||
const cfg = TestBed.configureTestingModule({
|
||||
imports: [SettingsV2Component, RouterTestingModule],
|
||||
providers: [
|
||||
{ provide: AccountService, useValue: mockAccountService },
|
||||
{ provide: BillingAccountProfileStateService, useValue: mockBillingState },
|
||||
{ provide: NudgesService, useValue: mockNudges },
|
||||
{ provide: AutofillBrowserSettingsService, useValue: mockAutofillSettings },
|
||||
{ provide: DialogService, useValue: dialogService },
|
||||
{ provide: I18nService, useValue: { t: jest.fn((key: string) => key) } },
|
||||
{ provide: GlobalStateProvider, useValue: new FakeGlobalStateProvider() },
|
||||
{ provide: PlatformUtilsService, useValue: mock<PlatformUtilsService>() },
|
||||
{ provide: AvatarService, useValue: mock<AvatarService>() },
|
||||
{ provide: AuthService, useValue: mock<AuthService>() },
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
});
|
||||
|
||||
TestBed.overrideComponent(SettingsV2Component, {
|
||||
add: {
|
||||
imports: [CurrentAccountStubComponent],
|
||||
providers: [{ provide: DialogService, useValue: dialogService }],
|
||||
},
|
||||
remove: {
|
||||
imports: [CurrentAccountComponent],
|
||||
},
|
||||
});
|
||||
|
||||
await cfg.compileComponents();
|
||||
}));
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
function pushActiveAccount(id = "user-123"): Account {
|
||||
const acct = { id } as Account;
|
||||
account$.next(acct);
|
||||
return acct;
|
||||
}
|
||||
|
||||
it("shows the premium spotlight when user does NOT have premium", async () => {
|
||||
mockBillingState.hasPremiumFromAnySource$.mockReturnValue(of(false));
|
||||
pushActiveAccount();
|
||||
|
||||
const fixture = TestBed.createComponent(SettingsV2Component);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const el: HTMLElement = fixture.nativeElement;
|
||||
|
||||
expect(el.querySelector("bit-spotlight")).toBeTruthy();
|
||||
});
|
||||
|
||||
it("hides the premium spotlight when user HAS premium", async () => {
|
||||
mockBillingState.hasPremiumFromAnySource$.mockReturnValue(of(true));
|
||||
pushActiveAccount();
|
||||
|
||||
const fixture = TestBed.createComponent(SettingsV2Component);
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const el: HTMLElement = fixture.nativeElement;
|
||||
expect(el.querySelector("bit-spotlight")).toBeFalsy();
|
||||
});
|
||||
|
||||
it("openUpgradeDialog calls PremiumUpgradeDialogComponent.open with the DialogService", async () => {
|
||||
openSpy = jest.spyOn(PremiumUpgradeDialogComponent, "open").mockImplementation();
|
||||
mockBillingState.hasPremiumFromAnySource$.mockReturnValue(of(false));
|
||||
pushActiveAccount();
|
||||
|
||||
const fixture = TestBed.createComponent(SettingsV2Component);
|
||||
const component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
component["openUpgradeDialog"]();
|
||||
expect(openSpy).toHaveBeenCalledTimes(1);
|
||||
expect(openSpy).toHaveBeenCalledWith(dialogService);
|
||||
});
|
||||
|
||||
it("isBrowserAutofillSettingOverridden$ emits the value from the AutofillBrowserSettingsService", async () => {
|
||||
pushActiveAccount();
|
||||
|
||||
mockAutofillSettings.isBrowserAutofillSettingOverridden.mockResolvedValue(true);
|
||||
|
||||
const fixture = TestBed.createComponent(SettingsV2Component);
|
||||
const component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const value = await firstValueFrom(component["isBrowserAutofillSettingOverridden$"]);
|
||||
expect(value).toBe(true);
|
||||
|
||||
mockAutofillSettings.isBrowserAutofillSettingOverridden.mockResolvedValue(false);
|
||||
|
||||
const fixture2 = TestBed.createComponent(SettingsV2Component);
|
||||
const component2 = fixture2.componentInstance;
|
||||
fixture2.detectChanges();
|
||||
await fixture2.whenStable();
|
||||
|
||||
const value2 = await firstValueFrom(component2["isBrowserAutofillSettingOverridden$"]);
|
||||
expect(value2).toBe(false);
|
||||
});
|
||||
|
||||
it("showAutofillBadge$ emits true when default autofill is NOT disabled and nudge is true", async () => {
|
||||
pushActiveAccount();
|
||||
|
||||
mockNudges.showNudgeBadge$.mockImplementation((type: NudgeType) =>
|
||||
of(type === NudgeType.AutofillNudge),
|
||||
);
|
||||
|
||||
const fixture = TestBed.createComponent(SettingsV2Component);
|
||||
const component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
mockAutofillSettings.defaultBrowserAutofillDisabled$.next(false);
|
||||
|
||||
const value = await firstValueFrom(component.showAutofillBadge$);
|
||||
expect(value).toBe(true);
|
||||
});
|
||||
|
||||
it("showAutofillBadge$ emits false when default autofill IS disabled even if nudge is true", async () => {
|
||||
pushActiveAccount();
|
||||
|
||||
mockNudges.showNudgeBadge$.mockImplementation((type: NudgeType) =>
|
||||
of(type === NudgeType.AutofillNudge),
|
||||
);
|
||||
|
||||
const fixture = TestBed.createComponent(SettingsV2Component);
|
||||
const component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
mockAutofillSettings.defaultBrowserAutofillDisabled$.next(true);
|
||||
|
||||
const value = await firstValueFrom(component.showAutofillBadge$);
|
||||
expect(value).toBe(false);
|
||||
});
|
||||
|
||||
it("dismissBadge dismisses when showVaultBadge$ emits true", async () => {
|
||||
const acct = pushActiveAccount();
|
||||
|
||||
mockNudges.showNudgeBadge$.mockImplementation((type: NudgeType) => {
|
||||
return of(type === NudgeType.EmptyVaultNudge);
|
||||
});
|
||||
|
||||
const fixture = TestBed.createComponent(SettingsV2Component);
|
||||
const component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
await component.dismissBadge(NudgeType.EmptyVaultNudge);
|
||||
|
||||
expect(mockNudges.dismissNudge).toHaveBeenCalledTimes(1);
|
||||
expect(mockNudges.dismissNudge).toHaveBeenCalledWith(NudgeType.EmptyVaultNudge, acct.id, true);
|
||||
});
|
||||
|
||||
it("dismissBadge does nothing when showVaultBadge$ emits false", async () => {
|
||||
pushActiveAccount();
|
||||
|
||||
mockNudges.showNudgeBadge$.mockReturnValue(of(false));
|
||||
|
||||
const fixture = TestBed.createComponent(SettingsV2Component);
|
||||
const component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
await component.dismissBadge(NudgeType.EmptyVaultNudge);
|
||||
|
||||
expect(mockNudges.dismissNudge).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("showDownloadBitwardenNudge$ proxies to nudges service for the active account", async () => {
|
||||
const acct = pushActiveAccount("user-xyz");
|
||||
|
||||
mockNudges.showNudgeBadge$.mockImplementation((type: NudgeType) =>
|
||||
of(type === NudgeType.DownloadBitwarden),
|
||||
);
|
||||
|
||||
const fixture = TestBed.createComponent(SettingsV2Component);
|
||||
const component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
await fixture.whenStable();
|
||||
|
||||
const val = await firstValueFrom(component.showDownloadBitwardenNudge$);
|
||||
expect(val).toBe(true);
|
||||
expect(mockNudges.showNudgeBadge$).toHaveBeenCalledWith(NudgeType.DownloadBitwarden, acct.id);
|
||||
});
|
||||
});
|
||||
@@ -1,21 +1,31 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { ChangeDetectionStrategy, Component } from "@angular/core";
|
||||
import { RouterModule } from "@angular/router";
|
||||
import {
|
||||
combineLatest,
|
||||
filter,
|
||||
firstValueFrom,
|
||||
from,
|
||||
map,
|
||||
Observable,
|
||||
shareReplay,
|
||||
switchMap,
|
||||
} from "rxjs";
|
||||
|
||||
import { PremiumUpgradeDialogComponent } from "@bitwarden/angular/billing/components";
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { NudgesService, NudgeType } from "@bitwarden/angular/vault";
|
||||
import { SpotlightComponent } from "@bitwarden/angular/vault/components/spotlight/spotlight.component";
|
||||
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { BadgeComponent, ItemModule } from "@bitwarden/components";
|
||||
import {
|
||||
BadgeComponent,
|
||||
DialogService,
|
||||
ItemModule,
|
||||
LinkModule,
|
||||
TypographyModule,
|
||||
} from "@bitwarden/components";
|
||||
|
||||
import { CurrentAccountComponent } from "../../../auth/popup/account-switching/current-account.component";
|
||||
import { AutofillBrowserSettingsService } from "../../../autofill/services/autofill-browser-settings.service";
|
||||
@@ -24,8 +34,6 @@ import { PopOutComponent } from "../../../platform/popup/components/pop-out.comp
|
||||
import { PopupHeaderComponent } from "../../../platform/popup/layout/popup-header.component";
|
||||
import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.component";
|
||||
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
|
||||
@Component({
|
||||
templateUrl: "settings-v2.component.html",
|
||||
imports: [
|
||||
@@ -38,18 +46,30 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co
|
||||
ItemModule,
|
||||
CurrentAccountComponent,
|
||||
BadgeComponent,
|
||||
SpotlightComponent,
|
||||
TypographyModule,
|
||||
LinkModule,
|
||||
],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class SettingsV2Component implements OnInit {
|
||||
export class SettingsV2Component {
|
||||
NudgeType = NudgeType;
|
||||
activeUserId: UserId | null = null;
|
||||
protected isBrowserAutofillSettingOverridden = false;
|
||||
|
||||
protected isBrowserAutofillSettingOverridden$ = from(
|
||||
this.autofillBrowserSettingsService.isBrowserAutofillSettingOverridden(
|
||||
BrowserApi.getBrowserClientVendor(window),
|
||||
),
|
||||
);
|
||||
|
||||
private authenticatedAccount$: Observable<Account> = this.accountService.activeAccount$.pipe(
|
||||
filter((account): account is Account => account !== null),
|
||||
shareReplay({ bufferSize: 1, refCount: true }),
|
||||
);
|
||||
|
||||
protected hasPremium$ = this.authenticatedAccount$.pipe(
|
||||
switchMap((account) => this.accountProfileStateService.hasPremiumFromAnySource$(account.id)),
|
||||
);
|
||||
|
||||
showDownloadBitwardenNudge$: Observable<boolean> = this.authenticatedAccount$.pipe(
|
||||
switchMap((account) =>
|
||||
this.nudgesService.showNudgeBadge$(NudgeType.DownloadBitwarden, account.id),
|
||||
@@ -79,13 +99,12 @@ export class SettingsV2Component implements OnInit {
|
||||
private readonly nudgesService: NudgesService,
|
||||
private readonly accountService: AccountService,
|
||||
private readonly autofillBrowserSettingsService: AutofillBrowserSettingsService,
|
||||
private readonly accountProfileStateService: BillingAccountProfileStateService,
|
||||
private readonly dialogService: DialogService,
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.isBrowserAutofillSettingOverridden =
|
||||
await this.autofillBrowserSettingsService.isBrowserAutofillSettingOverridden(
|
||||
BrowserApi.getBrowserClientVendor(window),
|
||||
);
|
||||
protected openUpgradeDialog() {
|
||||
PremiumUpgradeDialogComponent.open(this.dialogService);
|
||||
}
|
||||
|
||||
async dismissBadge(type: NudgeType) {
|
||||
|
||||
@@ -6,12 +6,6 @@
|
||||
</popup-header>
|
||||
|
||||
<bit-item-group>
|
||||
<bit-item *ngIf="!(canAccessPremium$ | async)">
|
||||
<a type="button" bit-item-content routerLink="/premium">
|
||||
{{ "premiumMembership" | i18n }}
|
||||
<i slot="end" class="bwi bwi-angle-right" aria-hidden="true"></i>
|
||||
</a>
|
||||
</bit-item>
|
||||
<bit-item
|
||||
*ngIf="
|
||||
(familySponsorshipAvailable$ | async) &&
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component } from "@angular/core";
|
||||
import { RouterModule } from "@angular/router";
|
||||
import { Observable, firstValueFrom, of, switchMap } from "rxjs";
|
||||
import { Observable, firstValueFrom, switchMap } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { DialogService, ItemModule } from "@bitwarden/components";
|
||||
|
||||
@@ -32,14 +31,12 @@ import { PopupPageComponent } from "../../../platform/popup/layout/popup-page.co
|
||||
],
|
||||
})
|
||||
export class MoreFromBitwardenPageV2Component {
|
||||
canAccessPremium$: Observable<boolean>;
|
||||
protected familySponsorshipAvailable$: Observable<boolean>;
|
||||
protected isFreeFamilyPolicyEnabled$: Observable<boolean>;
|
||||
protected hasSingleEnterpriseOrg$: Observable<boolean>;
|
||||
|
||||
constructor(
|
||||
private dialogService: DialogService,
|
||||
private billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
private environmentService: EnvironmentService,
|
||||
private organizationService: OrganizationService,
|
||||
private familiesPolicyService: FamiliesPolicyService,
|
||||
@@ -48,13 +45,6 @@ export class MoreFromBitwardenPageV2Component {
|
||||
this.familySponsorshipAvailable$ = getUserId(this.accountService.activeAccount$).pipe(
|
||||
switchMap((userId) => this.organizationService.familySponsorshipAvailable$(userId)),
|
||||
);
|
||||
this.canAccessPremium$ = this.accountService.activeAccount$.pipe(
|
||||
switchMap((account) =>
|
||||
account
|
||||
? this.billingAccountProfileStateService.hasPremiumFromAnySource$(account.id)
|
||||
: of(false),
|
||||
),
|
||||
);
|
||||
this.hasSingleEnterpriseOrg$ = this.familiesPolicyService.hasSingleEnterpriseOrg$();
|
||||
this.isFreeFamilyPolicyEnabled$ = this.familiesPolicyService.isFreeFamilyPolicyEnabled$();
|
||||
}
|
||||
|
||||
5
apps/desktop/desktop_native/Cargo.lock
generated
5
apps/desktop/desktop_native/Cargo.lock
generated
@@ -120,9 +120,9 @@ checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
|
||||
|
||||
[[package]]
|
||||
name = "arboard"
|
||||
version = "3.6.0"
|
||||
version = "3.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55f533f8e0af236ffe5eb979b99381df3258853f00ba2e44b6e1955292c75227"
|
||||
checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf"
|
||||
dependencies = [
|
||||
"clipboard-win",
|
||||
"log",
|
||||
@@ -131,6 +131,7 @@ dependencies = [
|
||||
"objc2-foundation",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"windows-sys 0.60.2",
|
||||
"wl-clipboard-rs",
|
||||
"x11rb",
|
||||
]
|
||||
|
||||
@@ -22,7 +22,7 @@ publish = false
|
||||
aes = "=0.8.4"
|
||||
aes-gcm = "=0.10.3"
|
||||
anyhow = "=1.0.94"
|
||||
arboard = { version = "=3.6.0", default-features = false }
|
||||
arboard = { version = "=3.6.1", default-features = false }
|
||||
ashpd = "=0.11.0"
|
||||
base64 = "=0.22.1"
|
||||
bitwarden-russh = { git = "https://github.com/bitwarden/bitwarden-russh.git", rev = "a641316227227f8777fdf56ac9fa2d6b5f7fe662" }
|
||||
|
||||
@@ -154,45 +154,15 @@
|
||||
},
|
||||
"configurations": {
|
||||
"oss": {
|
||||
"buildTarget": "web:build:oss"
|
||||
},
|
||||
"oss-dev": {
|
||||
"buildTarget": "web:build:oss-dev"
|
||||
},
|
||||
"commercial": {
|
||||
"buildTarget": "web:build:commercial"
|
||||
},
|
||||
"commercial-dev": {
|
||||
"buildTarget": "web:build:commercial-dev"
|
||||
},
|
||||
"commercial-qa": {
|
||||
"buildTarget": "web:build:commercial-qa"
|
||||
},
|
||||
"commercial-cloud": {
|
||||
"buildTarget": "web:build:commercial-cloud"
|
||||
},
|
||||
"commercial-euprd": {
|
||||
"buildTarget": "web:build:commercial-euprd"
|
||||
},
|
||||
"commercial-euqa": {
|
||||
"buildTarget": "web:build:commercial-euqa"
|
||||
},
|
||||
"commercial-usdev": {
|
||||
"buildTarget": "web:build:commercial-usdev"
|
||||
},
|
||||
"commercial-ee": {
|
||||
"buildTarget": "web:build:commercial-ee"
|
||||
},
|
||||
"oss-selfhost": {
|
||||
"buildTarget": "web:build:oss-selfhost"
|
||||
},
|
||||
"oss-selfhost-dev": {
|
||||
"buildTarget": "web:build:oss-selfhost-dev"
|
||||
},
|
||||
"commercial-selfhost": {
|
||||
"buildTarget": "web:build:commercial-selfhost"
|
||||
},
|
||||
"commercial-selfhost-dev": {
|
||||
"buildTarget": "web:build:commercial-selfhost-dev"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,10 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { PasswordRepromptService, CipherFormConfigService } from "@bitwarden/vault";
|
||||
|
||||
import { HeaderModule } from "../../../../layouts/header/header.module";
|
||||
import { SharedModule } from "../../../../shared";
|
||||
import { OrganizationBadgeModule } from "../../../../vault/individual-vault/organization-badge/organization-badge.module";
|
||||
import { PipesModule } from "../../../../vault/individual-vault/pipes/pipes.module";
|
||||
import { RoutedVaultFilterBridgeService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter-bridge.service";
|
||||
import { RoutedVaultFilterService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter.service";
|
||||
import { AdminConsoleCipherFormConfigService } from "../../../../vault/org-vault/services/admin-console-cipher-form-config.service";
|
||||
@@ -38,7 +42,7 @@ import { ExposedPasswordsReportComponent as BaseExposedPasswordsReportComponent
|
||||
RoutedVaultFilterService,
|
||||
RoutedVaultFilterBridgeService,
|
||||
],
|
||||
standalone: false,
|
||||
imports: [SharedModule, HeaderModule, OrganizationBadgeModule, PipesModule],
|
||||
})
|
||||
export class ExposedPasswordsReportComponent
|
||||
extends BaseExposedPasswordsReportComponent
|
||||
|
||||
@@ -14,6 +14,10 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { CipherFormConfigService, PasswordRepromptService } from "@bitwarden/vault";
|
||||
|
||||
import { HeaderModule } from "../../../../layouts/header/header.module";
|
||||
import { SharedModule } from "../../../../shared";
|
||||
import { OrganizationBadgeModule } from "../../../../vault/individual-vault/organization-badge/organization-badge.module";
|
||||
import { PipesModule } from "../../../../vault/individual-vault/pipes/pipes.module";
|
||||
import { RoutedVaultFilterBridgeService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter-bridge.service";
|
||||
import { RoutedVaultFilterService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter.service";
|
||||
import { AdminConsoleCipherFormConfigService } from "../../../../vault/org-vault/services/admin-console-cipher-form-config.service";
|
||||
@@ -32,7 +36,7 @@ import { InactiveTwoFactorReportComponent as BaseInactiveTwoFactorReportComponen
|
||||
RoutedVaultFilterService,
|
||||
RoutedVaultFilterBridgeService,
|
||||
],
|
||||
standalone: false,
|
||||
imports: [SharedModule, HeaderModule, OrganizationBadgeModule, PipesModule],
|
||||
})
|
||||
export class InactiveTwoFactorReportComponent
|
||||
extends BaseInactiveTwoFactorReportComponent
|
||||
|
||||
@@ -18,6 +18,10 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { CipherFormConfigService, PasswordRepromptService } from "@bitwarden/vault";
|
||||
|
||||
import { HeaderModule } from "../../../../layouts/header/header.module";
|
||||
import { SharedModule } from "../../../../shared";
|
||||
import { OrganizationBadgeModule } from "../../../../vault/individual-vault/organization-badge/organization-badge.module";
|
||||
import { PipesModule } from "../../../../vault/individual-vault/pipes/pipes.module";
|
||||
import { RoutedVaultFilterBridgeService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter-bridge.service";
|
||||
import { RoutedVaultFilterService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter.service";
|
||||
import { AdminConsoleCipherFormConfigService } from "../../../../vault/org-vault/services/admin-console-cipher-form-config.service";
|
||||
@@ -37,7 +41,7 @@ import { ReusedPasswordsReportComponent as BaseReusedPasswordsReportComponent }
|
||||
RoutedVaultFilterService,
|
||||
RoutedVaultFilterBridgeService,
|
||||
],
|
||||
standalone: false,
|
||||
imports: [SharedModule, HeaderModule, OrganizationBadgeModule, PipesModule],
|
||||
})
|
||||
export class ReusedPasswordsReportComponent
|
||||
extends BaseReusedPasswordsReportComponent
|
||||
|
||||
@@ -18,6 +18,10 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { CipherFormConfigService, PasswordRepromptService } from "@bitwarden/vault";
|
||||
|
||||
import { HeaderModule } from "../../../../layouts/header/header.module";
|
||||
import { SharedModule } from "../../../../shared";
|
||||
import { OrganizationBadgeModule } from "../../../../vault/individual-vault/organization-badge/organization-badge.module";
|
||||
import { PipesModule } from "../../../../vault/individual-vault/pipes/pipes.module";
|
||||
import { RoutedVaultFilterBridgeService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter-bridge.service";
|
||||
import { RoutedVaultFilterService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter.service";
|
||||
import { AdminConsoleCipherFormConfigService } from "../../../../vault/org-vault/services/admin-console-cipher-form-config.service";
|
||||
@@ -37,7 +41,7 @@ import { UnsecuredWebsitesReportComponent as BaseUnsecuredWebsitesReportComponen
|
||||
RoutedVaultFilterService,
|
||||
RoutedVaultFilterBridgeService,
|
||||
],
|
||||
standalone: false,
|
||||
imports: [SharedModule, HeaderModule, OrganizationBadgeModule, PipesModule],
|
||||
})
|
||||
export class UnsecuredWebsitesReportComponent
|
||||
extends BaseUnsecuredWebsitesReportComponent
|
||||
|
||||
@@ -19,6 +19,10 @@ import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { DialogService } from "@bitwarden/components";
|
||||
import { CipherFormConfigService, PasswordRepromptService } from "@bitwarden/vault";
|
||||
|
||||
import { HeaderModule } from "../../../../layouts/header/header.module";
|
||||
import { SharedModule } from "../../../../shared";
|
||||
import { OrganizationBadgeModule } from "../../../../vault/individual-vault/organization-badge/organization-badge.module";
|
||||
import { PipesModule } from "../../../../vault/individual-vault/pipes/pipes.module";
|
||||
import { RoutedVaultFilterBridgeService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter-bridge.service";
|
||||
import { RoutedVaultFilterService } from "../../../../vault/individual-vault/vault-filter/services/routed-vault-filter.service";
|
||||
import { AdminConsoleCipherFormConfigService } from "../../../../vault/org-vault/services/admin-console-cipher-form-config.service";
|
||||
@@ -38,7 +42,7 @@ import { WeakPasswordsReportComponent as BaseWeakPasswordsReportComponent } from
|
||||
RoutedVaultFilterService,
|
||||
RoutedVaultFilterBridgeService,
|
||||
],
|
||||
standalone: false,
|
||||
imports: [SharedModule, HeaderModule, OrganizationBadgeModule, PipesModule],
|
||||
})
|
||||
export class WeakPasswordsReportComponent
|
||||
extends BaseWeakPasswordsReportComponent
|
||||
|
||||
@@ -30,6 +30,8 @@ import { NavigationProductSwitcherComponent } from "./navigation-switcher.compon
|
||||
selector: "[mockOrgs]",
|
||||
standalone: false,
|
||||
})
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/PM-28232): Use Directive suffix
|
||||
// eslint-disable-next-line @angular-eslint/directive-class-suffix
|
||||
class MockOrganizationService implements Partial<OrganizationService> {
|
||||
private static _orgs = new BehaviorSubject<Organization[]>([]);
|
||||
|
||||
@@ -49,6 +51,8 @@ class MockOrganizationService implements Partial<OrganizationService> {
|
||||
selector: "[mockProviders]",
|
||||
standalone: false,
|
||||
})
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/PM-28232): Use Directive suffix
|
||||
// eslint-disable-next-line @angular-eslint/directive-class-suffix
|
||||
class MockProviderService implements Partial<ProviderService> {
|
||||
private static _providers = new BehaviorSubject<Provider[]>([]);
|
||||
|
||||
|
||||
@@ -30,6 +30,8 @@ import { ProductSwitcherService } from "./shared/product-switcher.service";
|
||||
selector: "[mockOrgs]",
|
||||
standalone: false,
|
||||
})
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/PM-28232): Use Directive suffix
|
||||
// eslint-disable-next-line @angular-eslint/directive-class-suffix
|
||||
class MockOrganizationService implements Partial<OrganizationService> {
|
||||
private static _orgs = new BehaviorSubject<Organization[]>([]);
|
||||
|
||||
@@ -49,6 +51,8 @@ class MockOrganizationService implements Partial<OrganizationService> {
|
||||
selector: "[mockProviders]",
|
||||
standalone: false,
|
||||
})
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/PM-28232): Use Directive suffix
|
||||
// eslint-disable-next-line @angular-eslint/directive-class-suffix
|
||||
class MockProviderService implements Partial<ProviderService> {
|
||||
private static _providers = new BehaviorSubject<Provider[]>([]);
|
||||
|
||||
|
||||
@@ -7,16 +7,6 @@ import { VerifyRecoverDeleteComponent } from "../auth/verify-recover-delete.comp
|
||||
import { FreeBitwardenFamiliesComponent } from "../billing/members/free-bitwarden-families.component";
|
||||
import { SponsoredFamiliesComponent } from "../billing/settings/sponsored-families.component";
|
||||
import { SponsoringOrgRowComponent } from "../billing/settings/sponsoring-org-row.component";
|
||||
// eslint-disable-next-line no-restricted-imports -- Temporarily disabled until DIRT refactors these out of this module
|
||||
import { ExposedPasswordsReportComponent as OrgExposedPasswordsReportComponent } from "../dirt/reports/pages/organizations/exposed-passwords-report.component";
|
||||
// eslint-disable-next-line no-restricted-imports -- Temporarily disabled until DIRT refactors these out of this module
|
||||
import { InactiveTwoFactorReportComponent as OrgInactiveTwoFactorReportComponent } from "../dirt/reports/pages/organizations/inactive-two-factor-report.component";
|
||||
// eslint-disable-next-line no-restricted-imports -- Temporarily disabled until DIRT refactors these out of this module
|
||||
import { ReusedPasswordsReportComponent as OrgReusedPasswordsReportComponent } from "../dirt/reports/pages/organizations/reused-passwords-report.component";
|
||||
// eslint-disable-next-line no-restricted-imports -- Temporarily disabled until DIRT refactors these out of this module
|
||||
import { UnsecuredWebsitesReportComponent as OrgUnsecuredWebsitesReportComponent } from "../dirt/reports/pages/organizations/unsecured-websites-report.component";
|
||||
// eslint-disable-next-line no-restricted-imports -- Temporarily disabled until DIRT refactors these out of this module
|
||||
import { WeakPasswordsReportComponent as OrgWeakPasswordsReportComponent } from "../dirt/reports/pages/organizations/weak-passwords-report.component";
|
||||
import { RemovePasswordComponent } from "../key-management/key-connector/remove-password.component";
|
||||
import { HeaderModule } from "../layouts/header/header.module";
|
||||
import { OrganizationBadgeModule } from "../vault/individual-vault/organization-badge/organization-badge.module";
|
||||
@@ -29,11 +19,6 @@ import { SharedModule } from "./shared.module";
|
||||
@NgModule({
|
||||
imports: [SharedModule, HeaderModule, OrganizationBadgeModule, PipesModule],
|
||||
declarations: [
|
||||
OrgExposedPasswordsReportComponent,
|
||||
OrgInactiveTwoFactorReportComponent,
|
||||
OrgReusedPasswordsReportComponent,
|
||||
OrgUnsecuredWebsitesReportComponent,
|
||||
OrgWeakPasswordsReportComponent,
|
||||
RecoverDeleteComponent,
|
||||
RecoverTwoFactorComponent,
|
||||
RemovePasswordComponent,
|
||||
|
||||
@@ -109,10 +109,12 @@
|
||||
<i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i>
|
||||
{{ "copyUsername" | i18n }}
|
||||
</button>
|
||||
<button bitMenuItem type="button" appCopyField="password" [cipher]="cipher">
|
||||
<i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i>
|
||||
{{ "copyPassword" | i18n }}
|
||||
</button>
|
||||
@if (cipher.viewPassword) {
|
||||
<button bitMenuItem type="button" appCopyField="password" [cipher]="cipher">
|
||||
<i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i>
|
||||
{{ "copyPassword" | i18n }}
|
||||
</button>
|
||||
}
|
||||
<button bitMenuItem type="button" appCopyField="totp" [cipher]="cipher">
|
||||
<i class="bwi bwi-fw bwi-clone" aria-hidden="true"></i>
|
||||
{{ "copyVerificationCode" | i18n }}
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
import { OverlayContainer } from "@angular/cdk/overlay";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { RouterModule } from "@angular/router";
|
||||
import { mock } from "jest-mock-extended";
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
|
||||
import { JslibModule } from "@bitwarden/angular/jslib.module";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherType } from "@bitwarden/common/vault/enums";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { LoginView } from "@bitwarden/common/vault/models/view/login.view";
|
||||
import { CipherViewLike } from "@bitwarden/common/vault/utils/cipher-view-like-utils";
|
||||
import { IconButtonModule, MenuModule } from "@bitwarden/components";
|
||||
import { CopyCipherFieldDirective, CopyCipherFieldService } from "@bitwarden/vault";
|
||||
|
||||
import { OrganizationNameBadgeComponent } from "../../individual-vault/organization-badge/organization-name-badge.component";
|
||||
|
||||
import { VaultCipherRowComponent } from "./vault-cipher-row.component";
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
const originalError = console.error;
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.error = (...args) => {
|
||||
if (
|
||||
typeof args[0] === "object" &&
|
||||
(args[0] as Error).message.includes("Could not parse CSS stylesheet")
|
||||
) {
|
||||
// Opening the overlay container in tests causes stylesheets to be parsed,
|
||||
// which can lead to JSDOM unable to parse CSS errors. These can be ignored safely.
|
||||
return;
|
||||
}
|
||||
originalError(...args);
|
||||
};
|
||||
|
||||
describe("VaultCipherRowComponent", () => {
|
||||
let component: VaultCipherRowComponent<CipherViewLike>;
|
||||
let fixture: ComponentFixture<VaultCipherRowComponent<CipherViewLike>>;
|
||||
let overlayContainer: OverlayContainer;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [VaultCipherRowComponent, OrganizationNameBadgeComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterModule.forRoot([]),
|
||||
MenuModule,
|
||||
IconButtonModule,
|
||||
JslibModule,
|
||||
CopyCipherFieldDirective,
|
||||
],
|
||||
providers: [
|
||||
{ provide: I18nService, useValue: { t: (key: string) => key } },
|
||||
{
|
||||
provide: EnvironmentService,
|
||||
useValue: { environment$: new BehaviorSubject({}).asObservable() },
|
||||
},
|
||||
{
|
||||
provide: DomainSettingsService,
|
||||
useValue: { showFavicons$: new BehaviorSubject(false).asObservable() },
|
||||
},
|
||||
{ provide: CopyCipherFieldService, useValue: mock<CopyCipherFieldService>() },
|
||||
{ provide: AccountService, useValue: mock<AccountService>() },
|
||||
{ provide: CipherService, useValue: mock<CipherService>() },
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(VaultCipherRowComponent);
|
||||
component = fixture.componentInstance;
|
||||
overlayContainer = TestBed.inject(OverlayContainer);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
overlayContainer?.ngOnDestroy();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error = originalError;
|
||||
});
|
||||
|
||||
describe("copy password visibility", () => {
|
||||
let loginCipher: CipherView;
|
||||
|
||||
beforeEach(() => {
|
||||
loginCipher = new CipherView();
|
||||
loginCipher.id = "cipher-1";
|
||||
loginCipher.name = "Test Login";
|
||||
loginCipher.type = CipherType.Login;
|
||||
loginCipher.login = new LoginView();
|
||||
loginCipher.login.password = "test-password";
|
||||
loginCipher.organizationId = undefined;
|
||||
loginCipher.deletedDate = null;
|
||||
loginCipher.archivedDate = null;
|
||||
|
||||
component.cipher = loginCipher;
|
||||
component.disabled = false;
|
||||
});
|
||||
|
||||
const openMenuAndGetContent = (): string => {
|
||||
fixture.detectChanges();
|
||||
|
||||
const menuTrigger = fixture.nativeElement.querySelector(
|
||||
'button[biticonbutton="bwi-ellipsis-v"]',
|
||||
) as HTMLButtonElement;
|
||||
expect(menuTrigger).toBeTruthy();
|
||||
|
||||
menuTrigger.click();
|
||||
fixture.detectChanges();
|
||||
|
||||
return overlayContainer.getContainerElement().innerHTML;
|
||||
};
|
||||
|
||||
it("renders copy password button in menu when viewPassword is true", () => {
|
||||
component.cipher.viewPassword = true;
|
||||
|
||||
const overlayContent = openMenuAndGetContent();
|
||||
|
||||
expect(overlayContent).toContain('appcopyfield="password"');
|
||||
expect(overlayContent).toContain("copyPassword");
|
||||
});
|
||||
|
||||
it("does not render copy password button in menu when viewPassword is false", () => {
|
||||
component.cipher.viewPassword = false;
|
||||
|
||||
const overlayContent = openMenuAndGetContent();
|
||||
|
||||
expect(overlayContent).not.toContain('appcopyfield="password"');
|
||||
});
|
||||
|
||||
it("does not render copy password button in menu when viewPassword is undefined", () => {
|
||||
component.cipher.viewPassword = undefined;
|
||||
|
||||
const overlayContent = openMenuAndGetContent();
|
||||
|
||||
expect(overlayContent).not.toContain('appcopyfield="password"');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -13,9 +13,11 @@ const config = require(path.resolve(__dirname, "config.js"));
|
||||
const pjson = require(path.resolve(__dirname, "package.json"));
|
||||
|
||||
module.exports.getEnv = function getEnv(params) {
|
||||
const ENV = params.env || (process.env.ENV == null ? "development" : process.env.ENV);
|
||||
const NODE_ENV = process.env.NODE_ENV == null ? "development" : process.env.NODE_ENV;
|
||||
const LOGGING = process.env.LOGGING != "false";
|
||||
const ENV = params.env?.ENV ?? process.env?.ENV ?? "development";
|
||||
const NODE_ENV = params.env?.NODE_ENV ?? process.env?.NODE_ENV ?? "development";
|
||||
const LOGGING =
|
||||
params.env?.LOGGING ??
|
||||
(process.env?.LOGGING === undefined ? true : process.env.LOGGING !== "false");
|
||||
|
||||
return { ENV, NODE_ENV, LOGGING };
|
||||
};
|
||||
@@ -35,7 +37,11 @@ const DEFAULT_PARAMS = {
|
||||
* tsConfig: string;
|
||||
* outputPath?: string;
|
||||
* mode?: string;
|
||||
* env?: string;
|
||||
* env?: {
|
||||
* ENV?: string;
|
||||
* NODE_ENV?: string;
|
||||
* LOGGING?: boolean;
|
||||
* };
|
||||
* importAliases?: import("webpack").ResolveOptions["alias"];
|
||||
* }} params
|
||||
*/
|
||||
|
||||
@@ -15,6 +15,7 @@ module.exports = (webpackConfig, context) => {
|
||||
},
|
||||
tsConfig: "apps/web/tsconfig.build.json",
|
||||
outputPath: path.resolve(context.context.root, context.options.outputPath),
|
||||
env: context.options.env,
|
||||
});
|
||||
} else {
|
||||
return buildConfig({
|
||||
|
||||
Reference in New Issue
Block a user