mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +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({
|
||||
|
||||
@@ -3,6 +3,7 @@ const { buildConfig } = require(path.resolve(__dirname, "../../apps/web/webpack.
|
||||
|
||||
module.exports = (webpackConfig, context) => {
|
||||
const isNxBuild = context && context.options;
|
||||
|
||||
if (isNxBuild) {
|
||||
return buildConfig({
|
||||
configName: "Commercial",
|
||||
@@ -23,6 +24,7 @@ module.exports = (webpackConfig, context) => {
|
||||
alias: "@bitwarden/commercial-sdk-internal",
|
||||
},
|
||||
],
|
||||
env: context.options.env,
|
||||
});
|
||||
} else {
|
||||
return buildConfig({
|
||||
|
||||
@@ -63,7 +63,7 @@ export default tseslint.config(
|
||||
// TODO: Enable these.
|
||||
"@angular-eslint/component-class-suffix": "error",
|
||||
"@angular-eslint/contextual-lifecycle": "error",
|
||||
"@angular-eslint/directive-class-suffix": 0,
|
||||
"@angular-eslint/directive-class-suffix": "error",
|
||||
"@angular-eslint/no-empty-lifecycle-method": 0,
|
||||
"@angular-eslint/no-input-rename": 0,
|
||||
"@angular-eslint/no-inputs-metadata-property": "error",
|
||||
|
||||
@@ -24,6 +24,8 @@ import { KeyService } from "@bitwarden/key-management";
|
||||
selector: "app-user-verification",
|
||||
standalone: false,
|
||||
})
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/PM-28232): Use Directive suffix
|
||||
// eslint-disable-next-line @angular-eslint/directive-class-suffix
|
||||
export class UserVerificationComponent implements ControlValueAccessor, OnInit, OnDestroy {
|
||||
private _invalidSecret = false;
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
|
||||
@@ -45,6 +45,8 @@ export function _cipherListVirtualScrollStrategyFactory(cipherListDir: CipherLis
|
||||
},
|
||||
],
|
||||
})
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/PM-28232): Use Directive suffix
|
||||
// eslint-disable-next-line @angular-eslint/directive-class-suffix
|
||||
export class CipherListVirtualScroll extends CdkFixedSizeVirtualScroll {
|
||||
_scrollStrategy: CipherListVirtualScrollStrategy;
|
||||
|
||||
|
||||
@@ -3,20 +3,20 @@
|
||||
>
|
||||
<div class="tw-flex tw-justify-between tw-items-start tw-flex-grow">
|
||||
<div>
|
||||
<h2 bitTypography="h4" class="tw-font-medium !tw-mb-1">{{ title }}</h2>
|
||||
<h2 *ngIf="title()" bitTypography="h4" class="tw-font-medium !tw-mb-1">{{ title() }}</h2>
|
||||
<p
|
||||
*ngIf="subtitle"
|
||||
*ngIf="subtitle()"
|
||||
class="tw-text-main tw-mb-0"
|
||||
bitTypography="body2"
|
||||
[innerHTML]="subtitle"
|
||||
[innerHTML]="subtitle()"
|
||||
></p>
|
||||
<ng-content *ngIf="!subtitle"></ng-content>
|
||||
<ng-content *ngIf="!subtitle()"></ng-content>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
bitIconButton="bwi-close"
|
||||
size="small"
|
||||
*ngIf="!persistent"
|
||||
*ngIf="!persistent()"
|
||||
(click)="handleDismiss()"
|
||||
class="-tw-me-2"
|
||||
[label]="'close' | i18n"
|
||||
@@ -28,10 +28,10 @@
|
||||
bitButton
|
||||
type="button"
|
||||
buttonType="primary"
|
||||
*ngIf="buttonText"
|
||||
*ngIf="buttonText()"
|
||||
(click)="handleButtonClick($event)"
|
||||
>
|
||||
{{ buttonText }}
|
||||
<i *ngIf="buttonIcon" [ngClass]="buttonIcon" class="bwi tw-ml-1" aria-hidden="true"></i>
|
||||
{{ buttonText() }}
|
||||
<i *ngIf="buttonIcon()" [ngClass]="buttonIcon()" class="bwi tw-ml-1" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { ChangeDetectionStrategy, Component } from "@angular/core";
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { By } from "@angular/platform-browser";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
|
||||
import { SpotlightComponent } from "./spotlight.component";
|
||||
|
||||
describe("SpotlightComponent", () => {
|
||||
let fixture: ComponentFixture<SpotlightComponent>;
|
||||
let component: SpotlightComponent;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [SpotlightComponent],
|
||||
providers: [{ provide: I18nService, useValue: { t: (key: string) => key } }],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(SpotlightComponent);
|
||||
component = fixture.componentInstance;
|
||||
});
|
||||
|
||||
function detect(): void {
|
||||
fixture.detectChanges();
|
||||
}
|
||||
|
||||
it("should create", () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
describe("rendering when inputs are null", () => {
|
||||
it("should render without crashing when inputs are null/undefined", () => {
|
||||
// Explicitly drive the inputs to null to exercise template null branches
|
||||
fixture.componentRef.setInput("title", null);
|
||||
fixture.componentRef.setInput("subtitle", null);
|
||||
fixture.componentRef.setInput("buttonText", null);
|
||||
fixture.componentRef.setInput("buttonIcon", null);
|
||||
// persistent has a default, but drive it as well for coverage sanity
|
||||
fixture.componentRef.setInput("persistent", false);
|
||||
|
||||
expect(() => detect()).not.toThrow();
|
||||
|
||||
const root = fixture.debugElement.nativeElement as HTMLElement;
|
||||
expect(root).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe("close button visibility based on persistent", () => {
|
||||
it("should show the close button when persistent is false", () => {
|
||||
fixture.componentRef.setInput("persistent", false);
|
||||
detect();
|
||||
|
||||
// Assumes dismiss uses bitIconButton
|
||||
const dismissButton = fixture.debugElement.query(By.css("button[bitIconButton]"));
|
||||
|
||||
expect(dismissButton).toBeTruthy();
|
||||
});
|
||||
|
||||
it("should hide the close button when persistent is true", () => {
|
||||
fixture.componentRef.setInput("persistent", true);
|
||||
detect();
|
||||
|
||||
const dismissButton = fixture.debugElement.query(By.css("button[bitIconButton]"));
|
||||
expect(dismissButton).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("event emission", () => {
|
||||
it("should emit onButtonClick when CTA button is clicked", () => {
|
||||
const clickSpy = jest.fn();
|
||||
component.onButtonClick.subscribe(clickSpy);
|
||||
|
||||
fixture.componentRef.setInput("buttonText", "Click me");
|
||||
detect();
|
||||
|
||||
const buttonDe = fixture.debugElement.query(By.css("button[bitButton]"));
|
||||
expect(buttonDe).toBeTruthy();
|
||||
|
||||
const event = new MouseEvent("click");
|
||||
buttonDe.triggerEventHandler("click", event);
|
||||
|
||||
expect(clickSpy).toHaveBeenCalledTimes(1);
|
||||
expect(clickSpy.mock.calls[0][0]).toBeInstanceOf(MouseEvent);
|
||||
});
|
||||
|
||||
it("should emit onDismiss when close button is clicked", () => {
|
||||
const dismissSpy = jest.fn();
|
||||
component.onDismiss.subscribe(dismissSpy);
|
||||
|
||||
fixture.componentRef.setInput("persistent", false);
|
||||
detect();
|
||||
|
||||
const dismissButton = fixture.debugElement.query(By.css("button[bitIconButton]"));
|
||||
expect(dismissButton).toBeTruthy();
|
||||
|
||||
dismissButton.triggerEventHandler("click", new MouseEvent("click"));
|
||||
|
||||
expect(dismissSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("handleButtonClick should emit via onButtonClick()", () => {
|
||||
const clickSpy = jest.fn();
|
||||
component.onButtonClick.subscribe(clickSpy);
|
||||
|
||||
const event = new MouseEvent("click");
|
||||
component.handleButtonClick(event);
|
||||
|
||||
expect(clickSpy).toHaveBeenCalledTimes(1);
|
||||
expect(clickSpy.mock.calls[0][0]).toBe(event);
|
||||
});
|
||||
|
||||
it("handleDismiss should emit via onDismiss()", () => {
|
||||
const dismissSpy = jest.fn();
|
||||
component.onDismiss.subscribe(dismissSpy);
|
||||
|
||||
component.handleDismiss();
|
||||
|
||||
expect(dismissSpy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("content projection behavior", () => {
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [SpotlightComponent],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<bit-spotlight>
|
||||
<span class="tw-text-sm">Projected content</span>
|
||||
</bit-spotlight>
|
||||
`,
|
||||
})
|
||||
class HostWithProjectionComponent {}
|
||||
|
||||
let hostFixture: ComponentFixture<HostWithProjectionComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
hostFixture = TestBed.createComponent(HostWithProjectionComponent);
|
||||
});
|
||||
|
||||
it("should render projected content inside the spotlight", () => {
|
||||
hostFixture.detectChanges();
|
||||
|
||||
const projected = hostFixture.debugElement.query(By.css(".tw-text-sm"));
|
||||
expect(projected).toBeTruthy();
|
||||
expect(projected.nativeElement.textContent.trim()).toBe("Projected content");
|
||||
});
|
||||
});
|
||||
|
||||
describe("boolean attribute transform for persistent", () => {
|
||||
@Component({
|
||||
standalone: true,
|
||||
imports: [CommonModule, SpotlightComponent],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<!-- bare persistent attribute -->
|
||||
<bit-spotlight *ngIf="mode === 'bare'" persistent></bit-spotlight>
|
||||
|
||||
<!-- no persistent attribute -->
|
||||
<bit-spotlight *ngIf="mode === 'none'"></bit-spotlight>
|
||||
|
||||
<!-- explicit persistent="false" -->
|
||||
<bit-spotlight *ngIf="mode === 'falseStr'" persistent="false"></bit-spotlight>
|
||||
`,
|
||||
})
|
||||
class BooleanHostComponent {
|
||||
mode: "bare" | "none" | "falseStr" = "bare";
|
||||
}
|
||||
|
||||
let boolFixture: ComponentFixture<BooleanHostComponent>;
|
||||
let boolHost: BooleanHostComponent;
|
||||
|
||||
beforeEach(async () => {
|
||||
boolFixture = TestBed.createComponent(BooleanHostComponent);
|
||||
boolHost = boolFixture.componentInstance;
|
||||
});
|
||||
|
||||
function getSpotlight(): SpotlightComponent {
|
||||
const de = boolFixture.debugElement.query(By.directive(SpotlightComponent));
|
||||
return de.componentInstance as SpotlightComponent;
|
||||
}
|
||||
|
||||
it("treats bare 'persistent' attribute as true via booleanAttribute", () => {
|
||||
boolHost.mode = "bare";
|
||||
boolFixture.detectChanges();
|
||||
|
||||
const spotlight = getSpotlight();
|
||||
expect(spotlight.persistent()).toBe(true);
|
||||
});
|
||||
|
||||
it("uses default false when 'persistent' is omitted", () => {
|
||||
boolHost.mode = "none";
|
||||
boolFixture.detectChanges();
|
||||
|
||||
const spotlight = getSpotlight();
|
||||
expect(spotlight.persistent()).toBe(false);
|
||||
});
|
||||
|
||||
it('treats persistent="false" as false', () => {
|
||||
boolHost.mode = "falseStr";
|
||||
boolFixture.detectChanges();
|
||||
|
||||
const spotlight = getSpotlight();
|
||||
expect(spotlight.persistent()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,43 +1,28 @@
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||
import { booleanAttribute, ChangeDetectionStrategy, Component, input, output } from "@angular/core";
|
||||
|
||||
import { ButtonModule, IconButtonModule, TypographyModule } from "@bitwarden/components";
|
||||
import { I18nPipe } from "@bitwarden/ui-common";
|
||||
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush
|
||||
// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection
|
||||
@Component({
|
||||
selector: "bit-spotlight",
|
||||
templateUrl: "spotlight.component.html",
|
||||
imports: [ButtonModule, CommonModule, IconButtonModule, I18nPipe, TypographyModule],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class SpotlightComponent {
|
||||
// The title of the component
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-signals
|
||||
@Input({ required: true }) title: string | null = null;
|
||||
readonly title = input<string>();
|
||||
// The subtitle of the component
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-signals
|
||||
@Input() subtitle?: string | null = null;
|
||||
readonly subtitle = input<string>();
|
||||
// The text to display on the button
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-signals
|
||||
@Input() buttonText?: string;
|
||||
// Wheter the component can be dismissed, if true, the component will not show a close button
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-signals
|
||||
@Input() persistent = false;
|
||||
readonly buttonText = input<string>();
|
||||
// Whether the component can be dismissed, if true, the component will not show a close button
|
||||
readonly persistent = input(false, { transform: booleanAttribute });
|
||||
// Optional icon to display on the button
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-signals
|
||||
@Input() buttonIcon: string | null = null;
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref
|
||||
@Output() onDismiss = new EventEmitter<void>();
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals
|
||||
// eslint-disable-next-line @angular-eslint/prefer-output-emitter-ref
|
||||
@Output() onButtonClick = new EventEmitter();
|
||||
readonly buttonIcon = input<string>();
|
||||
readonly onDismiss = output<void>();
|
||||
readonly onButtonClick = output<MouseEvent>();
|
||||
|
||||
handleButtonClick(event: MouseEvent): void {
|
||||
this.onButtonClick.emit(event);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { BitwardenClient } from "@bitwarden/sdk-internal";
|
||||
import { PasswordManagerClient } from "@bitwarden/sdk-internal";
|
||||
|
||||
import { SdkClientFactory } from "../src/platform/abstractions/sdk/sdk-client-factory";
|
||||
|
||||
export class DefaultSdkClientFactory implements SdkClientFactory {
|
||||
createSdkClient(
|
||||
...args: ConstructorParameters<typeof BitwardenClient>
|
||||
): Promise<BitwardenClient> {
|
||||
...args: ConstructorParameters<typeof PasswordManagerClient>
|
||||
): Promise<PasswordManagerClient> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import type { BitwardenClient } from "@bitwarden/sdk-internal";
|
||||
import type { PasswordManagerClient } from "@bitwarden/sdk-internal";
|
||||
|
||||
/**
|
||||
* Factory for creating SDK clients.
|
||||
*/
|
||||
export abstract class SdkClientFactory {
|
||||
/**
|
||||
* Creates a new BitwardenClient. Assumes the SDK is already loaded.
|
||||
* @param args Bitwarden client constructor parameters
|
||||
* Creates a new Password Manager client. Assumes the SDK is already loaded.
|
||||
* @param args Password Manager client constructor parameters
|
||||
*/
|
||||
abstract createSdkClient(
|
||||
...args: ConstructorParameters<typeof BitwardenClient>
|
||||
): Promise<BitwardenClient>;
|
||||
...args: ConstructorParameters<typeof PasswordManagerClient>
|
||||
): Promise<PasswordManagerClient>;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { BitwardenClient, Uuid } from "@bitwarden/sdk-internal";
|
||||
import { PasswordManagerClient, Uuid } from "@bitwarden/sdk-internal";
|
||||
|
||||
import { UserId } from "../../../types/guid";
|
||||
import { Rc } from "../../misc/reference-counting/rc";
|
||||
@@ -46,7 +46,7 @@ export abstract class SdkService {
|
||||
* Retrieve a client initialized without a user.
|
||||
* This client can only be used for operations that don't require a user context.
|
||||
*/
|
||||
abstract client$: Observable<BitwardenClient>;
|
||||
abstract client$: Observable<PasswordManagerClient>;
|
||||
|
||||
/**
|
||||
* Retrieve a client initialized for a specific user.
|
||||
@@ -64,7 +64,7 @@ export abstract class SdkService {
|
||||
*
|
||||
* @param userId The user id for which to retrieve the client
|
||||
*/
|
||||
abstract userClient$(userId: UserId): Observable<Rc<BitwardenClient>>;
|
||||
abstract userClient$(userId: UserId): Observable<Rc<PasswordManagerClient>>;
|
||||
|
||||
/**
|
||||
* This method is used during/after an authentication procedure to set a new client for a specific user.
|
||||
@@ -75,5 +75,5 @@ export abstract class SdkService {
|
||||
* @param userId The user id for which to set the client
|
||||
* @param client The client to set for the user. If undefined, the client will be unset.
|
||||
*/
|
||||
abstract setClient(userId: UserId, client: BitwardenClient | undefined): void;
|
||||
abstract setClient(userId: UserId, client: PasswordManagerClient | undefined): void;
|
||||
}
|
||||
|
||||
@@ -7,13 +7,13 @@ import { SdkClientFactory } from "../../abstractions/sdk/sdk-client-factory";
|
||||
*/
|
||||
export class DefaultSdkClientFactory implements SdkClientFactory {
|
||||
/**
|
||||
* Initializes a Bitwarden client. Assumes the SDK is already loaded.
|
||||
* @param args Bitwarden client constructor parameters
|
||||
* @returns A BitwardenClient
|
||||
* Initializes a Password Manager client. Assumes the SDK is already loaded.
|
||||
* @param args Password Manager client constructor parameters
|
||||
* @returns A PasswordManagerClient
|
||||
*/
|
||||
async createSdkClient(
|
||||
...args: ConstructorParameters<typeof sdk.BitwardenClient>
|
||||
): Promise<sdk.BitwardenClient> {
|
||||
return Promise.resolve(new sdk.BitwardenClient(...args));
|
||||
...args: ConstructorParameters<typeof sdk.PasswordManagerClient>
|
||||
): Promise<sdk.PasswordManagerClient> {
|
||||
return Promise.resolve(new sdk.PasswordManagerClient(...args));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { SecurityStateService } from "@bitwarden/common/key-management/security-
|
||||
// This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop.
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { KdfConfigService, KeyService, PBKDF2KdfConfig } from "@bitwarden/key-management";
|
||||
import { BitwardenClient } from "@bitwarden/sdk-internal";
|
||||
import { PasswordManagerClient } from "@bitwarden/sdk-internal";
|
||||
|
||||
import {
|
||||
ObservableTracker,
|
||||
@@ -109,7 +109,7 @@ describe("DefaultSdkService", () => {
|
||||
});
|
||||
|
||||
describe("given no client override has been set for the user", () => {
|
||||
let mockClient!: MockProxy<BitwardenClient>;
|
||||
let mockClient!: MockProxy<PasswordManagerClient>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockClient = createMockClient();
|
||||
@@ -123,8 +123,8 @@ describe("DefaultSdkService", () => {
|
||||
});
|
||||
|
||||
it("does not create an SDK client when called the second time with same userId", async () => {
|
||||
const subject_1 = new BehaviorSubject<Rc<BitwardenClient> | undefined>(undefined);
|
||||
const subject_2 = new BehaviorSubject<Rc<BitwardenClient> | undefined>(undefined);
|
||||
const subject_1 = new BehaviorSubject<Rc<PasswordManagerClient> | undefined>(undefined);
|
||||
const subject_2 = new BehaviorSubject<Rc<PasswordManagerClient> | undefined>(undefined);
|
||||
|
||||
// Use subjects to ensure the subscription is kept alive
|
||||
service.userClient$(userId).subscribe(subject_1);
|
||||
@@ -139,8 +139,8 @@ describe("DefaultSdkService", () => {
|
||||
});
|
||||
|
||||
it("destroys the internal SDK client when all subscriptions are closed", async () => {
|
||||
const subject_1 = new BehaviorSubject<Rc<BitwardenClient> | undefined>(undefined);
|
||||
const subject_2 = new BehaviorSubject<Rc<BitwardenClient> | undefined>(undefined);
|
||||
const subject_1 = new BehaviorSubject<Rc<PasswordManagerClient> | undefined>(undefined);
|
||||
const subject_2 = new BehaviorSubject<Rc<PasswordManagerClient> | undefined>(undefined);
|
||||
const subscription_1 = service.userClient$(userId).subscribe(subject_1);
|
||||
const subscription_2 = service.userClient$(userId).subscribe(subject_2);
|
||||
await new Promise(process.nextTick);
|
||||
@@ -170,7 +170,7 @@ describe("DefaultSdkService", () => {
|
||||
|
||||
describe("given overrides are used", () => {
|
||||
it("does not create a new client and emits the override client when a client override has already been set ", async () => {
|
||||
const mockClient = mock<BitwardenClient>();
|
||||
const mockClient = mock<PasswordManagerClient>();
|
||||
service.setClient(userId, mockClient);
|
||||
const userClientTracker = new ObservableTracker(service.userClient$(userId), false);
|
||||
await userClientTracker.pauseUntilReceived(1);
|
||||
@@ -242,8 +242,8 @@ describe("DefaultSdkService", () => {
|
||||
});
|
||||
});
|
||||
|
||||
function createMockClient(): MockProxy<BitwardenClient> {
|
||||
const client = mock<BitwardenClient>();
|
||||
function createMockClient(): MockProxy<PasswordManagerClient> {
|
||||
const client = mock<PasswordManagerClient>();
|
||||
client.crypto.mockReturnValue(mock());
|
||||
client.platform.mockReturnValue({
|
||||
state: jest.fn().mockReturnValue(mock()),
|
||||
|
||||
@@ -20,7 +20,7 @@ import { ConfigService } from "@bitwarden/common/platform/abstractions/config/co
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { KeyService, KdfConfigService, KdfConfig, KdfType } from "@bitwarden/key-management";
|
||||
import {
|
||||
BitwardenClient,
|
||||
PasswordManagerClient,
|
||||
ClientSettings,
|
||||
DeviceType as SdkDeviceType,
|
||||
TokenProvider,
|
||||
@@ -70,9 +70,9 @@ class JsTokenProvider implements TokenProvider {
|
||||
|
||||
export class DefaultSdkService implements SdkService {
|
||||
private sdkClientOverrides = new BehaviorSubject<{
|
||||
[userId: UserId]: Rc<BitwardenClient> | typeof UnsetClient;
|
||||
[userId: UserId]: Rc<PasswordManagerClient> | typeof UnsetClient;
|
||||
}>({});
|
||||
private sdkClientCache = new Map<UserId, Observable<Rc<BitwardenClient>>>();
|
||||
private sdkClientCache = new Map<UserId, Observable<Rc<PasswordManagerClient>>>();
|
||||
|
||||
client$ = this.environmentService.environment$.pipe(
|
||||
concatMap(async (env) => {
|
||||
@@ -107,14 +107,14 @@ export class DefaultSdkService implements SdkService {
|
||||
private userAgent: string | null = null,
|
||||
) {}
|
||||
|
||||
userClient$(userId: UserId): Observable<Rc<BitwardenClient>> {
|
||||
userClient$(userId: UserId): Observable<Rc<PasswordManagerClient>> {
|
||||
return this.sdkClientOverrides.pipe(
|
||||
takeWhile((clients) => clients[userId] !== UnsetClient, false),
|
||||
map((clients) => {
|
||||
if (clients[userId] === UnsetClient) {
|
||||
throw new Error("Encountered UnsetClient even though it should have been filtered out");
|
||||
}
|
||||
return clients[userId] as Rc<BitwardenClient>;
|
||||
return clients[userId] as Rc<PasswordManagerClient>;
|
||||
}),
|
||||
distinctUntilChanged(),
|
||||
switchMap((clientOverride) => {
|
||||
@@ -129,7 +129,7 @@ export class DefaultSdkService implements SdkService {
|
||||
);
|
||||
}
|
||||
|
||||
setClient(userId: UserId, client: BitwardenClient | undefined) {
|
||||
setClient(userId: UserId, client: PasswordManagerClient | undefined) {
|
||||
const previousValue = this.sdkClientOverrides.value[userId];
|
||||
|
||||
this.sdkClientOverrides.next({
|
||||
@@ -149,7 +149,7 @@ export class DefaultSdkService implements SdkService {
|
||||
* @param userId The user id for which to create the client
|
||||
* @returns An observable that emits the client for the user
|
||||
*/
|
||||
private internalClient$(userId: UserId): Observable<Rc<BitwardenClient>> {
|
||||
private internalClient$(userId: UserId): Observable<Rc<PasswordManagerClient>> {
|
||||
const cached = this.sdkClientCache.get(userId);
|
||||
if (cached !== undefined) {
|
||||
return cached;
|
||||
@@ -187,7 +187,7 @@ export class DefaultSdkService implements SdkService {
|
||||
switchMap(
|
||||
([env, account, kdfParams, privateKey, userKey, signingKey, orgKeys, securityState]) => {
|
||||
// Create our own observable to be able to implement clean-up logic
|
||||
return new Observable<Rc<BitwardenClient>>((subscriber) => {
|
||||
return new Observable<Rc<PasswordManagerClient>>((subscriber) => {
|
||||
const createAndInitializeClient = async () => {
|
||||
if (env == null || kdfParams == null || privateKey == null || userKey == null) {
|
||||
return undefined;
|
||||
@@ -214,7 +214,7 @@ export class DefaultSdkService implements SdkService {
|
||||
return client;
|
||||
};
|
||||
|
||||
let client: Rc<BitwardenClient> | undefined;
|
||||
let client: Rc<PasswordManagerClient> | undefined;
|
||||
createAndInitializeClient()
|
||||
.then((c) => {
|
||||
client = c === undefined ? undefined : new Rc(c);
|
||||
@@ -239,7 +239,7 @@ export class DefaultSdkService implements SdkService {
|
||||
|
||||
private async initializeClient(
|
||||
userId: UserId,
|
||||
client: BitwardenClient,
|
||||
client: PasswordManagerClient,
|
||||
account: AccountInfo,
|
||||
kdfParams: KdfConfig,
|
||||
privateKey: EncryptedString,
|
||||
@@ -281,7 +281,7 @@ export class DefaultSdkService implements SdkService {
|
||||
await this.loadFeatureFlags(client);
|
||||
}
|
||||
|
||||
private async loadFeatureFlags(client: BitwardenClient) {
|
||||
private async loadFeatureFlags(client: PasswordManagerClient) {
|
||||
const serverConfig = await firstValueFrom(this.configService.serverConfig$);
|
||||
|
||||
const featureFlagMap = new Map(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { BitwardenClient } from "@bitwarden/sdk-internal";
|
||||
import type { PasswordManagerClient } from "@bitwarden/sdk-internal";
|
||||
|
||||
import { SdkClientFactory } from "../../abstractions/sdk/sdk-client-factory";
|
||||
|
||||
@@ -9,8 +9,8 @@ import { SdkClientFactory } from "../../abstractions/sdk/sdk-client-factory";
|
||||
*/
|
||||
export class NoopSdkClientFactory implements SdkClientFactory {
|
||||
createSdkClient(
|
||||
...args: ConstructorParameters<typeof BitwardenClient>
|
||||
): Promise<BitwardenClient> {
|
||||
...args: ConstructorParameters<typeof PasswordManagerClient>
|
||||
): Promise<PasswordManagerClient> {
|
||||
return Promise.reject(new Error("SDK not available"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
throwIfEmpty,
|
||||
} from "rxjs";
|
||||
|
||||
import { BitwardenClient } from "@bitwarden/sdk-internal";
|
||||
import { PasswordManagerClient } from "@bitwarden/sdk-internal";
|
||||
|
||||
import { UserId } from "../../types/guid";
|
||||
import { SdkService, UserNotLoggedInError } from "../abstractions/sdk/sdk.service";
|
||||
@@ -17,18 +17,18 @@ import { DeepMockProxy, mockDeep } from "./mock-deep";
|
||||
|
||||
export class MockSdkService implements SdkService {
|
||||
private userClients$ = new BehaviorSubject<{
|
||||
[userId: UserId]: Rc<BitwardenClient> | undefined;
|
||||
[userId: UserId]: Rc<PasswordManagerClient> | undefined;
|
||||
}>({});
|
||||
|
||||
private _client$ = new BehaviorSubject(mockDeep<BitwardenClient>());
|
||||
private _client$ = new BehaviorSubject(mockDeep<PasswordManagerClient>());
|
||||
client$ = this._client$.asObservable();
|
||||
|
||||
version$ = new BehaviorSubject("0.0.1-test").asObservable();
|
||||
|
||||
userClient$(userId: UserId): Observable<Rc<BitwardenClient>> {
|
||||
userClient$(userId: UserId): Observable<Rc<PasswordManagerClient>> {
|
||||
return this.userClients$.pipe(
|
||||
takeWhile((clients) => clients[userId] !== undefined, false),
|
||||
map((clients) => clients[userId] as Rc<BitwardenClient>),
|
||||
map((clients) => clients[userId] as Rc<PasswordManagerClient>),
|
||||
distinctUntilChanged(),
|
||||
throwIfEmpty(() => new UserNotLoggedInError(userId)),
|
||||
);
|
||||
@@ -42,7 +42,7 @@ export class MockSdkService implements SdkService {
|
||||
* Returns the non-user scoped client mock.
|
||||
* This is what is returned by the `client$` observable.
|
||||
*/
|
||||
get client(): DeepMockProxy<BitwardenClient> {
|
||||
get client(): DeepMockProxy<PasswordManagerClient> {
|
||||
return this._client$.value;
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ export class MockSdkService implements SdkService {
|
||||
* @returns A user-scoped mock for the user.
|
||||
*/
|
||||
userLogin: (userId: UserId) => {
|
||||
const client = mockDeep<BitwardenClient>();
|
||||
const client = mockDeep<PasswordManagerClient>();
|
||||
this.userClients$.next({
|
||||
...this.userClients$.getValue(),
|
||||
[userId]: new Rc(client),
|
||||
|
||||
@@ -54,6 +54,14 @@ const buttonStyles: Record<ButtonType, string[]> = {
|
||||
"hover:!tw-text-contrast",
|
||||
...focusRing,
|
||||
],
|
||||
dangerPrimary: [
|
||||
"tw-border-danger-600",
|
||||
"tw-bg-danger-600",
|
||||
"!tw-text-contrast",
|
||||
"hover:tw-bg-danger-700",
|
||||
"hover:tw-border-danger-700",
|
||||
...focusRing,
|
||||
],
|
||||
unstyled: [],
|
||||
};
|
||||
|
||||
|
||||
@@ -62,6 +62,13 @@ export const Primary: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const DangerPrimary: Story = {
|
||||
...Default,
|
||||
args: {
|
||||
buttonType: "dangerPrimary",
|
||||
},
|
||||
};
|
||||
|
||||
export const Danger: Story = {
|
||||
...Default,
|
||||
args: {
|
||||
@@ -77,6 +84,7 @@ export const Small: Story = {
|
||||
<button type="button" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="'primary'" [size]="size" [block]="block">Primary small</button>
|
||||
<button type="button" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="'secondary'" [size]="size" [block]="block">Secondary small</button>
|
||||
<button type="button" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="'danger'" [size]="size" [block]="block">Danger small</button>
|
||||
<button type="button" bitButton [disabled]="disabled" [loading]="loading" [buttonType]="'dangerPrimary'" [size]="size" [block]="block">Danger Primary small</button>
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
|
||||
import { FormControlComponent } from "./form-control.component";
|
||||
import { BitHintComponent } from "./hint.component";
|
||||
import { BitHintDirective } from "./hint.directive";
|
||||
import { BitLabelComponent } from "./label.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [BitLabelComponent, FormControlComponent, BitHintComponent],
|
||||
exports: [FormControlComponent, BitLabelComponent, BitHintComponent],
|
||||
imports: [BitLabelComponent, FormControlComponent, BitHintDirective],
|
||||
exports: [FormControlComponent, BitLabelComponent, BitHintDirective],
|
||||
})
|
||||
export class FormControlModule {}
|
||||
|
||||
@@ -9,6 +9,6 @@ let nextId = 0;
|
||||
class: "tw-text-muted tw-font-normal tw-inline-block tw-mt-1 tw-text-xs",
|
||||
},
|
||||
})
|
||||
export class BitHintComponent {
|
||||
export class BitHintDirective {
|
||||
@HostBinding() id = `bit-hint-${nextId++}`;
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
|
||||
import { I18nPipe } from "@bitwarden/ui-common";
|
||||
|
||||
import { BitHintComponent } from "../form-control/hint.component";
|
||||
import { BitHintDirective } from "../form-control/hint.directive";
|
||||
import { BitLabelComponent } from "../form-control/label.component";
|
||||
import { inputBorderClasses } from "../input/input.directive";
|
||||
|
||||
@@ -31,7 +31,7 @@ import { BitFormFieldControl } from "./form-field-control";
|
||||
})
|
||||
export class BitFormFieldComponent implements AfterContentChecked {
|
||||
readonly input = contentChild.required(BitFormFieldControl);
|
||||
readonly hint = contentChild(BitHintComponent);
|
||||
readonly hint = contentChild(BitHintDirective);
|
||||
readonly label = contentChild(BitLabelComponent);
|
||||
|
||||
readonly prefixContainer = viewChild<ElementRef<HTMLDivElement>>("prefixContainer");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ModelSignal } from "@angular/core";
|
||||
|
||||
export type ButtonType = "primary" | "secondary" | "danger" | "unstyled";
|
||||
export type ButtonType = "primary" | "secondary" | "danger" | "dangerPrimary" | "unstyled";
|
||||
|
||||
export type ButtonSize = "default" | "small";
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms";
|
||||
|
||||
import { AriaDisableDirective } from "../a11y";
|
||||
import { FormControlModule } from "../form-control/form-control.module";
|
||||
import { BitHintComponent } from "../form-control/hint.component";
|
||||
import { BitHintDirective } from "../form-control/hint.directive";
|
||||
import { BitLabelComponent } from "../form-control/label.component";
|
||||
|
||||
let nextId = 0;
|
||||
@@ -56,7 +56,7 @@ export class SwitchComponent implements ControlValueAccessor, AfterViewInit {
|
||||
protected readonly disabled = model(false);
|
||||
protected readonly disabledReasonText = input<string | null>(null);
|
||||
|
||||
private readonly hintComponent = contentChild<BitHintComponent>(BitHintComponent);
|
||||
private readonly hintComponent = contentChild<BitHintDirective>(BitHintDirective);
|
||||
|
||||
protected readonly disabledReasonTextId = `bit-switch-disabled-text-${nextId++}`;
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ import { TableComponent } from "./table.component";
|
||||
@Directive({
|
||||
selector: "[bitRowDef]",
|
||||
})
|
||||
export class BitRowDef {
|
||||
export class BitRowDefDirective {
|
||||
constructor(public template: TemplateRef<any>) {}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ export class TableScrollComponent
|
||||
/** Optional trackBy function. */
|
||||
readonly trackBy = input<TrackByFunction<any> | undefined>();
|
||||
|
||||
protected readonly rowDef = contentChild(BitRowDef);
|
||||
protected readonly rowDef = contentChild(BitRowDefDirective);
|
||||
|
||||
/**
|
||||
* Height of the thead element (in pixels).
|
||||
|
||||
@@ -5,14 +5,14 @@ import { NgModule } from "@angular/core";
|
||||
import { CellDirective } from "./cell.directive";
|
||||
import { RowDirective } from "./row.directive";
|
||||
import { SortableComponent } from "./sortable.component";
|
||||
import { BitRowDef, TableScrollComponent } from "./table-scroll.component";
|
||||
import { BitRowDefDirective, TableScrollComponent } from "./table-scroll.component";
|
||||
import { TableBodyDirective, TableComponent } from "./table.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
ScrollingModule,
|
||||
BitRowDef,
|
||||
BitRowDefDirective,
|
||||
CellDirective,
|
||||
RowDirective,
|
||||
SortableComponent,
|
||||
@@ -21,7 +21,7 @@ import { TableBodyDirective, TableComponent } from "./table.component";
|
||||
TableScrollComponent,
|
||||
],
|
||||
exports: [
|
||||
BitRowDef,
|
||||
BitRowDefDirective,
|
||||
CellDirective,
|
||||
RowDirective,
|
||||
SortableComponent,
|
||||
|
||||
@@ -8,6 +8,8 @@ import { firstValueFrom } from "rxjs";
|
||||
providers: [TextFieldModule],
|
||||
hostDirectives: [CdkTextareaAutosize],
|
||||
})
|
||||
// FIXME(https://bitwarden.atlassian.net/browse/PM-28232): Use Directive suffix
|
||||
// eslint-disable-next-line @angular-eslint/directive-class-suffix
|
||||
export class VaultAutosizeReadOnlyTextArea implements AfterViewInit {
|
||||
constructor(
|
||||
@Host() private autosize: CdkTextareaAutosize,
|
||||
|
||||
156
package-lock.json
generated
156
package-lock.json
generated
@@ -23,8 +23,8 @@
|
||||
"@angular/platform-browser": "19.2.14",
|
||||
"@angular/platform-browser-dynamic": "19.2.14",
|
||||
"@angular/router": "19.2.14",
|
||||
"@bitwarden/commercial-sdk-internal": "0.2.0-main.375",
|
||||
"@bitwarden/sdk-internal": "0.2.0-main.375",
|
||||
"@bitwarden/commercial-sdk-internal": "0.2.0-main.395",
|
||||
"@bitwarden/sdk-internal": "0.2.0-main.395",
|
||||
"@electron/fuses": "1.8.0",
|
||||
"@emotion/css": "11.13.5",
|
||||
"@koa/multer": "4.0.0",
|
||||
@@ -38,7 +38,7 @@
|
||||
"@nx/js": "21.6.8",
|
||||
"@nx/webpack": "21.6.8",
|
||||
"big-integer": "1.6.52",
|
||||
"braintree-web-drop-in": "1.44.0",
|
||||
"braintree-web-drop-in": "1.46.0",
|
||||
"buffer": "6.0.3",
|
||||
"bufferutil": "4.0.9",
|
||||
"chalk": "4.1.2",
|
||||
@@ -84,7 +84,7 @@
|
||||
"@compodoc/compodoc": "1.1.26",
|
||||
"@electron/notarize": "3.0.1",
|
||||
"@electron/rebuild": "4.0.1",
|
||||
"@eslint/compat": "1.2.9",
|
||||
"@eslint/compat": "2.0.0",
|
||||
"@lit-labs/signals": "0.1.2",
|
||||
"@ngtools/webpack": "19.2.14",
|
||||
"@storybook/addon-a11y": "8.6.12",
|
||||
@@ -4620,9 +4620,9 @@
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@bitwarden/commercial-sdk-internal": {
|
||||
"version": "0.2.0-main.375",
|
||||
"resolved": "https://registry.npmjs.org/@bitwarden/commercial-sdk-internal/-/commercial-sdk-internal-0.2.0-main.375.tgz",
|
||||
"integrity": "sha512-UMVfLjMh79+5et1if7qqOi+pSGP5Ay3AcGp4E5oLZ0p0yFsN2Q54UFv+SLju0/oI0qTvVZP1RkEtTJXHdNrpTg==",
|
||||
"version": "0.2.0-main.395",
|
||||
"resolved": "https://registry.npmjs.org/@bitwarden/commercial-sdk-internal/-/commercial-sdk-internal-0.2.0-main.395.tgz",
|
||||
"integrity": "sha512-DrxL3iA29hzWpyxPyZjiXx0m+EHOgk4CVb+BAi2SoxsacmyHYuTgXuASFMieRz2rv85wS3UR0N64Ok9lC+xNYA==",
|
||||
"license": "BITWARDEN SOFTWARE DEVELOPMENT KIT LICENSE AGREEMENT",
|
||||
"dependencies": {
|
||||
"type-fest": "^4.41.0"
|
||||
@@ -4725,9 +4725,9 @@
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@bitwarden/sdk-internal": {
|
||||
"version": "0.2.0-main.375",
|
||||
"resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.375.tgz",
|
||||
"integrity": "sha512-kf2SKFkAdSmV2/ORo6u1eegwYW2ha62NHUsx2ij2uPWmm7mzXUoNa7z8mqhJV1ozg5o7yBqBuXd6Wqo9Ww+/RA==",
|
||||
"version": "0.2.0-main.395",
|
||||
"resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.395.tgz",
|
||||
"integrity": "sha512-biExeL2Grp11VQjjK6QM16+WOYk87mTgUhYKFm+Bu/A0zZBzhL/6AocpA9h2T5M8rLCGVVJVUMaXUW3YrSTqEA==",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"type-fest": "^4.41.0"
|
||||
@@ -4798,15 +4798,15 @@
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@braintree/asset-loader": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@braintree/asset-loader/-/asset-loader-2.0.1.tgz",
|
||||
"integrity": "sha512-OGAoBA5MRVsr5qg0sXM6NMJbqHnYZhBudtM6WGgpQnoX42fjUYbE6Y6qFuuerD5z3lsOAjnu80DooBs1VBuh5Q==",
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@braintree/asset-loader/-/asset-loader-2.0.3.tgz",
|
||||
"integrity": "sha512-uREap1j30wKRlC0mK99nNPMpEp77NtB6XixpDfFJPZHmkrmw7IB4skKe+26LZBK1H6oSainFhAyKoP7x3eyOKA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@braintree/browser-detection": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@braintree/browser-detection/-/browser-detection-2.0.1.tgz",
|
||||
"integrity": "sha512-wpRI7AXEUh6o3ILrJbpNOYE7ItfjX/S8JZP7Z5FF66ULngBGYOqE8SeLlLKXG69Nc07HtlL/6nk/h539iz9hcQ==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@braintree/browser-detection/-/browser-detection-2.0.2.tgz",
|
||||
"integrity": "sha512-Zrv/pyodvwv/hsjsBKXKVcwHZOkx4A/5Cy2hViXtqghAhLd3483bYUIfHZJE5JKTrd018ny1FI5pN1PHFtW7vw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@braintree/event-emitter": {
|
||||
@@ -4822,9 +4822,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@braintree/iframer": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@braintree/iframer/-/iframer-2.0.0.tgz",
|
||||
"integrity": "sha512-x1kHOyIJNDvi4P1s6pVBZhqhBa1hqDG9+yzcsCR1oNVC0LxH9CAP8bKxioT8/auY1sUyy+D8T4Vp/jv7QqSqLQ==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@braintree/iframer/-/iframer-2.0.1.tgz",
|
||||
"integrity": "sha512-t1zJX5+f1yxHAzBJPaQT/XVMocKodUqjTE+hYvuxxWjqEZIbH8/eT5b5n767jY16mYw3+XiDkKHqcp4Pclq1wg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@braintree/sanitize-url": {
|
||||
@@ -4834,9 +4834,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@braintree/uuid": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@braintree/uuid/-/uuid-1.0.0.tgz",
|
||||
"integrity": "sha512-AtI5hfttWSuWAgcwLUZdcZ7Fp/8jCCUf9JTs7+Xow9ditU28zuoBovqq083yph2m3SxPYb84lGjOq+cXlXBvJg==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@braintree/uuid/-/uuid-1.0.1.tgz",
|
||||
"integrity": "sha512-Tgu5GoODkf4oj4aLlVIapEPEfjitIHrg5ftqY6pa5Ghr4ZUA9XtZIIZ6ZPdP9x8/X0lt/FB8tRq83QuCQCwOrA==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@braintree/wrap-promise": {
|
||||
@@ -6559,16 +6559,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/compat": {
|
||||
"version": "1.2.9",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.2.9.tgz",
|
||||
"integrity": "sha512-gCdSY54n7k+driCadyMNv8JSPzYLeDVM/ikZRtvtROBpRdFSkS8W9A82MqsaY7lZuwL0wiapgD0NT1xT0hyJsA==",
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-2.0.0.tgz",
|
||||
"integrity": "sha512-T9AfE1G1uv4wwq94ozgTGio5EUQBqAVe1X9qsQtSNVEYW6j3hvtZVm8Smr4qL1qDPFg+lOB2cL5RxTRMzq4CTA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@eslint/core": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
"node": "^20.19.0 || ^22.13.0 || >=24"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^9.10.0"
|
||||
"eslint": "^8.40 || 9"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"eslint": {
|
||||
@@ -6576,6 +6579,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/compat/node_modules/@eslint/core": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.0.0.tgz",
|
||||
"integrity": "sha512-PRfWP+8FOldvbApr6xL7mNCw4cJcSTq4GA7tYbgq15mRb0kWKO/wEB2jr+uwjFH3sZvEZneZyCUGTxsv4Sahyw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.15"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.13.0 || >=24"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/config-array": {
|
||||
"version": "0.20.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz",
|
||||
@@ -17762,40 +17778,40 @@
|
||||
}
|
||||
},
|
||||
"node_modules/braintree-web": {
|
||||
"version": "3.113.0",
|
||||
"resolved": "https://registry.npmjs.org/braintree-web/-/braintree-web-3.113.0.tgz",
|
||||
"integrity": "sha512-qykYxZyld4X1tRNgXZQ3ZGzmhDGTBTRQ6Q24KaG9PuYqo+P2TVDEDOVC6tRbkx2RUIdXLv2M6WpkG7oLqEia9Q==",
|
||||
"version": "3.123.2",
|
||||
"resolved": "https://registry.npmjs.org/braintree-web/-/braintree-web-3.123.2.tgz",
|
||||
"integrity": "sha512-N4IH75vKY67eONc0Ao4e7F+XagFW+3ok+Nfs/eOjw5D/TUt03diMAQ8woOwJghi2ql6/yjqNzZi2zE/sTWXmJg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@braintree/asset-loader": "2.0.1",
|
||||
"@braintree/browser-detection": "2.0.1",
|
||||
"@braintree/asset-loader": "2.0.3",
|
||||
"@braintree/browser-detection": "2.0.2",
|
||||
"@braintree/event-emitter": "0.4.1",
|
||||
"@braintree/extended-promise": "1.0.0",
|
||||
"@braintree/iframer": "2.0.0",
|
||||
"@braintree/iframer": "2.0.1",
|
||||
"@braintree/sanitize-url": "7.0.4",
|
||||
"@braintree/uuid": "1.0.0",
|
||||
"@braintree/uuid": "1.0.1",
|
||||
"@braintree/wrap-promise": "2.1.0",
|
||||
"@paypal/accelerated-checkout-loader": "1.1.0",
|
||||
"card-validator": "10.0.0",
|
||||
"credit-card-type": "10.0.1",
|
||||
"framebus": "6.0.0",
|
||||
"inject-stylesheet": "6.0.1",
|
||||
"card-validator": "10.0.3",
|
||||
"credit-card-type": "10.0.2",
|
||||
"framebus": "6.0.3",
|
||||
"inject-stylesheet": "6.0.2",
|
||||
"promise-polyfill": "8.2.3",
|
||||
"restricted-input": "3.0.5"
|
||||
"restricted-input": "4.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/braintree-web-drop-in": {
|
||||
"version": "1.44.0",
|
||||
"resolved": "https://registry.npmjs.org/braintree-web-drop-in/-/braintree-web-drop-in-1.44.0.tgz",
|
||||
"integrity": "sha512-maOq9SwiXztIzixJhOras7K44x4UIqqnkyQMYAJqxQ8WkADv9AkflCu2j3IeVYCus/Th9gWWFHcBugn3C4sZGw==",
|
||||
"version": "1.46.0",
|
||||
"resolved": "https://registry.npmjs.org/braintree-web-drop-in/-/braintree-web-drop-in-1.46.0.tgz",
|
||||
"integrity": "sha512-KxCjJpaigoMajYD/iIA+ohXaI6Olt2Bj/Yu45WpJOjolKO9n1UmXl9bsq9UIiGOFIGqi/JWva1wI4cIHHvcI1A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@braintree/asset-loader": "2.0.1",
|
||||
"@braintree/browser-detection": "2.0.1",
|
||||
"@braintree/asset-loader": "2.0.3",
|
||||
"@braintree/browser-detection": "2.0.2",
|
||||
"@braintree/event-emitter": "0.4.1",
|
||||
"@braintree/uuid": "1.0.0",
|
||||
"@braintree/uuid": "1.0.1",
|
||||
"@braintree/wrap-promise": "2.1.0",
|
||||
"braintree-web": "3.113.0"
|
||||
"braintree-web": "3.123.2"
|
||||
}
|
||||
},
|
||||
"node_modules/browser-assert": {
|
||||
@@ -18444,20 +18460,14 @@
|
||||
"license": "CC-BY-4.0"
|
||||
},
|
||||
"node_modules/card-validator": {
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/card-validator/-/card-validator-10.0.0.tgz",
|
||||
"integrity": "sha512-2fLyCBOxO7/b56sxoYav8FeJqv9bWpZSyKq8sXKxnpxTGXHnM/0c8WEKG+ZJ+OXFcabnl98pD0EKBtTn+Tql0g==",
|
||||
"version": "10.0.3",
|
||||
"resolved": "https://registry.npmjs.org/card-validator/-/card-validator-10.0.3.tgz",
|
||||
"integrity": "sha512-xOEDsK3hojV0OIpmrR64eZGpngnOqRDEP20O+WSRtvjLSW6nyekW4i2N9SzYg679uFO3RyHcFHxb+mml5tXc4A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"credit-card-type": "^9.1.0"
|
||||
"credit-card-type": "^10.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/card-validator/node_modules/credit-card-type": {
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/credit-card-type/-/credit-card-type-9.1.0.tgz",
|
||||
"integrity": "sha512-CpNFuLxiPFxuZqhSKml3M+t0K/484pMAnfYWH14JoD7OZMnmC0Lmo+P7JX9SobqFpRoo7ifA18kOHdxJywYPEA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/case-sensitive-paths-webpack-plugin": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz",
|
||||
@@ -19692,9 +19702,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/credit-card-type": {
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmjs.org/credit-card-type/-/credit-card-type-10.0.1.tgz",
|
||||
"integrity": "sha512-vQOuWmBgsgG1ovGeDi8m6Zeu1JaqH/JncrxKmaqMbv/LunyOQdLiQhPHtOsNlbUI05TocR5nod/Mbs3HYtr6sQ==",
|
||||
"version": "10.0.2",
|
||||
"resolved": "https://registry.npmjs.org/credit-card-type/-/credit-card-type-10.0.2.tgz",
|
||||
"integrity": "sha512-vt/iQokU0mtrT7ceRU75FSmWnIh5JFpLsUUUWYRmztYekOGm0ZbCuzwFTbNkq41k92y+0B8ChscFhRN9DhVZEA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cross-dirname": {
|
||||
@@ -23410,20 +23420,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/framebus": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/framebus/-/framebus-6.0.0.tgz",
|
||||
"integrity": "sha512-bL9V68hVaVBCY9rveoWbPFFI9hAXIJtESs51B+9XmzvMt38+wP8b4VdiJsavjMS6NfPZ/afQ/jc2qaHmSGI1kQ==",
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/framebus/-/framebus-6.0.3.tgz",
|
||||
"integrity": "sha512-G/N2p+kFZ1xPBge7tbtTq2KcTR1kSKs1rVbTqH//WdtvJSexS33fsTTOq3yfUWvUczqhujyaFc+omawC9YyRBg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@braintree/uuid": "^0.1.0"
|
||||
"@braintree/uuid": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/framebus/node_modules/@braintree/uuid": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@braintree/uuid/-/uuid-0.1.0.tgz",
|
||||
"integrity": "sha512-YvZJdlNcK5EnR+7M8AjgEAf4Qx696+FOSYlPfy5ePn80vODtVAUU0FxHnzKZC0og1VbDNQDDiwhthR65D4Na0g==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/fresh": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
|
||||
@@ -24987,9 +24991,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/inject-stylesheet": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/inject-stylesheet/-/inject-stylesheet-6.0.1.tgz",
|
||||
"integrity": "sha512-2fvune1D4+8mvJoLVo95ncY4HrDkIaYIReRzXv8tkWFgdG9iuc5QuX57gtSDPWTWQI/f5BGwwtH85wxHouzucg==",
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/inject-stylesheet/-/inject-stylesheet-6.0.2.tgz",
|
||||
"integrity": "sha512-sswMueya1LXEfwcy7KXPuq3zAW6HvgAeViApEhIaCviCkP4XYoKrQj8ftEmxPmIHn88X4R3xOAsnN/QCPvVKWw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/inquirer": {
|
||||
@@ -36154,12 +36158,12 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/restricted-input": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/restricted-input/-/restricted-input-3.0.5.tgz",
|
||||
"integrity": "sha512-lUuXZ3wUnHURRarj5/0C8vomWIfWJO+p7T6RYwB46v7Oyuyr3yyupU+i7SjqUv4S6RAeAAZt1C/QCLJ9xhQBow==",
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/restricted-input/-/restricted-input-4.0.3.tgz",
|
||||
"integrity": "sha512-VpkwT5Fr3DhvoRZfPnmHDhnYAYETjjNzDlvA4NlW0iknFS47C5X4OCHEpOOxaPjvmka5V8d1ty1jVVoorZKvHg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@braintree/browser-detection": "^1.12.1"
|
||||
"@braintree/browser-detection": "^1.17.2"
|
||||
}
|
||||
},
|
||||
"node_modules/restricted-input/node_modules/@braintree/browser-detection": {
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
"@compodoc/compodoc": "1.1.26",
|
||||
"@electron/notarize": "3.0.1",
|
||||
"@electron/rebuild": "4.0.1",
|
||||
"@eslint/compat": "1.2.9",
|
||||
"@eslint/compat": "2.0.0",
|
||||
"@lit-labs/signals": "0.1.2",
|
||||
"@ngtools/webpack": "19.2.14",
|
||||
"@storybook/addon-a11y": "8.6.12",
|
||||
@@ -160,8 +160,8 @@
|
||||
"@angular/platform-browser": "19.2.14",
|
||||
"@angular/platform-browser-dynamic": "19.2.14",
|
||||
"@angular/router": "19.2.14",
|
||||
"@bitwarden/sdk-internal": "0.2.0-main.375",
|
||||
"@bitwarden/commercial-sdk-internal": "0.2.0-main.375",
|
||||
"@bitwarden/sdk-internal": "0.2.0-main.395",
|
||||
"@bitwarden/commercial-sdk-internal": "0.2.0-main.395",
|
||||
"@electron/fuses": "1.8.0",
|
||||
"@emotion/css": "11.13.5",
|
||||
"@koa/multer": "4.0.0",
|
||||
@@ -175,7 +175,7 @@
|
||||
"@nx/js": "21.6.8",
|
||||
"@nx/webpack": "21.6.8",
|
||||
"big-integer": "1.6.52",
|
||||
"braintree-web-drop-in": "1.44.0",
|
||||
"braintree-web-drop-in": "1.46.0",
|
||||
"buffer": "6.0.3",
|
||||
"bufferutil": "4.0.9",
|
||||
"chalk": "4.1.2",
|
||||
|
||||
Reference in New Issue
Block a user