- |
+ |
-
+
{{ "criticalApplicationsWithCount" | i18n: criticalApps.length }}
diff --git a/apps/web/src/app/tools/risk-insights/risk-insights.component.ts b/apps/web/src/app/tools/risk-insights/risk-insights.component.ts
index 43d6da70e96..1c6a36b4454 100644
--- a/apps/web/src/app/tools/risk-insights/risk-insights.component.ts
+++ b/apps/web/src/app/tools/risk-insights/risk-insights.component.ts
@@ -1,9 +1,11 @@
import { CommonModule } from "@angular/common";
-import { Component } from "@angular/core";
+import { Component, OnInit } from "@angular/core";
import { takeUntilDestroyed } from "@angular/core/rxjs-interop";
import { ActivatedRoute, Router } from "@angular/router";
import { JslibModule } from "@bitwarden/angular/jslib.module";
+import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
+import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { AsyncActionsModule, ButtonModule, TabsModule } from "@bitwarden/components";
import { HeaderModule } from "../../layouts/header/header.module";
@@ -39,9 +41,10 @@ export enum RiskInsightsTabType {
TabsModule,
],
})
-export class RiskInsightsComponent {
+export class RiskInsightsComponent implements OnInit {
tabIndex: RiskInsightsTabType;
dataLastUpdated = new Date();
+ isCritialAppsFeatureEnabled = false;
apps: any[] = [];
criticalApps: any[] = [];
@@ -65,9 +68,16 @@ export class RiskInsightsComponent {
});
};
+ async ngOnInit() {
+ this.isCritialAppsFeatureEnabled = await this.configService.getFeatureFlag(
+ FeatureFlag.CriticalApps,
+ );
+ }
+
constructor(
protected route: ActivatedRoute,
private router: Router,
+ private configService: ConfigService,
) {
route.queryParams.pipe(takeUntilDestroyed()).subscribe(({ tabIndex }) => {
this.tabIndex = !isNaN(tabIndex) ? tabIndex : RiskInsightsTabType.AllApps;
diff --git a/apps/web/src/app/vault/individual-vault/add-edit.component.html b/apps/web/src/app/vault/individual-vault/add-edit.component.html
index 855b5dac489..01ac60fc7e6 100644
--- a/apps/web/src/app/vault/individual-vault/add-edit.component.html
+++ b/apps/web/src/app/vault/individual-vault/add-edit.component.html
@@ -851,6 +851,99 @@
+
+
+
+
-
+
plan.type === PlanType.TeamsMonthly);
- const enterprisePlan = this.dialogParams.plans.find(
- (plan) => plan.type === PlanType.EnterpriseMonthly,
- );
-
this.discountPercentage = response.discountPercentage;
const discountFactor = this.discountPercentage ? (100 - this.discountPercentage) / 100 : 1;
- this.planCards = [
- {
- name: this.i18nService.t("planNameTeams"),
- cost: teamsPlan.PasswordManager.providerPortalSeatPrice * discountFactor,
- type: teamsPlan.type,
- plan: teamsPlan,
- selected: true,
- },
- {
- name: this.i18nService.t("planNameEnterprise"),
- cost: enterprisePlan.PasswordManager.providerPortalSeatPrice * discountFactor,
- type: enterprisePlan.type,
- plan: enterprisePlan,
- selected: false,
- },
- ];
+ this.planCards = [];
+
+ for (let i = 0; i < this.providerPlans.length; i++) {
+ const providerPlan = this.providerPlans[i];
+ const plan = this.dialogParams.plans.find((plan) => plan.type === providerPlan.type);
+
+ let planName: string;
+ switch (plan.productTier) {
+ case ProductTierType.Teams: {
+ planName = this.i18nService.t("planNameTeams");
+ break;
+ }
+ case ProductTierType.Enterprise: {
+ planName = this.i18nService.t("planNameEnterprise");
+ break;
+ }
+ }
+
+ this.planCards.push({
+ name: planName,
+ cost: plan.PasswordManager.providerPortalSeatPrice * discountFactor,
+ type: plan.type,
+ plan: plan,
+ selected: i === 0,
+ });
+ }
this.loading = false;
}
diff --git a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.html b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.html
index 6c4bf422f7a..f08dbf0c37a 100644
--- a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.html
+++ b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.html
@@ -4,7 +4,7 @@
- {{ "billingPlan" | i18n }}
- - {{ "providerPlan" | i18n }}
+ - {{ plan | i18n }}
- {{ data.status.label }}
-
diff --git a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.ts
index c3ad875136e..dea7d4ca197 100644
--- a/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.ts
+++ b/bitwarden_license/bit-web/src/app/billing/providers/subscription/provider-subscription-status.component.ts
@@ -1,6 +1,7 @@
import { DatePipe } from "@angular/common";
import { Component, Input } from "@angular/core";
+import { ProviderType } from "@bitwarden/common/admin-console/enums";
import { ProviderSubscriptionResponse } from "@bitwarden/common/billing/models/response/provider-subscription-response";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -32,6 +33,15 @@ export class ProviderSubscriptionStatusComponent {
private i18nService: I18nService,
) {}
+ get plan(): string {
+ switch (this.subscription.providerType) {
+ case ProviderType.Msp:
+ return "managedServiceProvider";
+ case ProviderType.MultiOrganizationEnterprise:
+ return "multiOrganizationEnterprise";
+ }
+ }
+
get status(): string {
if (this.subscription.cancelAt && this.subscription.status === "active") {
return "pending_cancellation";
diff --git a/libs/angular/src/vault/components/add-edit.component.ts b/libs/angular/src/vault/components/add-edit.component.ts
index aa661236f4c..952f2071e91 100644
--- a/libs/angular/src/vault/components/add-edit.component.ts
+++ b/libs/angular/src/vault/components/add-edit.component.ts
@@ -14,7 +14,8 @@ import { OrganizationUserStatusType, PolicyType } from "@bitwarden/common/admin-
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { normalizeExpiryYearFormat } from "@bitwarden/common/autofill/utils";
-import { EventType } from "@bitwarden/common/enums";
+import { ClientType, EventType } from "@bitwarden/common/enums";
+import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -36,6 +37,7 @@ import { IdentityView } from "@bitwarden/common/vault/models/view/identity.view"
import { LoginUriView } from "@bitwarden/common/vault/models/view/login-uri.view";
import { LoginView } from "@bitwarden/common/vault/models/view/login.view";
import { SecureNoteView } from "@bitwarden/common/vault/models/view/secure-note.view";
+import { SshKeyView } from "@bitwarden/common/vault/models/view/ssh-key.view";
import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service";
import { DialogService } from "@bitwarden/components";
import { PasswordRepromptService } from "@bitwarden/vault";
@@ -71,6 +73,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
restorePromise: Promise;
checkPasswordPromise: Promise;
showPassword = false;
+ showPrivateKey = false;
showTotpSeed = false;
showCardNumber = false;
showCardCode = false;
@@ -134,6 +137,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
{ name: i18nService.t("typeIdentity"), value: CipherType.Identity },
{ name: i18nService.t("typeSecureNote"), value: CipherType.SecureNote },
];
+
this.cardBrandOptions = [
{ name: "-- " + i18nService.t("select") + " --", value: null },
{ name: "Visa", value: "Visa" },
@@ -200,6 +204,11 @@ export class AddEditComponent implements OnInit, OnDestroy {
this.writeableCollections = await this.loadCollections();
this.canUseReprompt = await this.passwordRepromptService.enabled();
+
+ const sshKeysEnabled = await this.configService.getFeatureFlag(FeatureFlag.SSHKeyVaultItem);
+ if (this.platformUtilsService.getClientType() == ClientType.Desktop && sshKeysEnabled) {
+ this.typeOptions.push({ name: this.i18nService.t("typeSshKey"), value: CipherType.SshKey });
+ }
}
ngOnDestroy() {
@@ -279,6 +288,7 @@ export class AddEditComponent implements OnInit, OnDestroy {
this.cipher.identity = new IdentityView();
this.cipher.secureNote = new SecureNoteView();
this.cipher.secureNote.type = SecureNoteType.Generic;
+ this.cipher.sshKey = new SshKeyView();
this.cipher.reprompt = CipherRepromptType.None;
}
}
@@ -601,6 +611,10 @@ export class AddEditComponent implements OnInit, OnDestroy {
}
}
+ togglePrivateKey() {
+ this.showPrivateKey = !this.showPrivateKey;
+ }
+
toggleUriOptions(uri: LoginUriView) {
const u = uri as any;
u.showOptions = u.showOptions == null && uri.match != null ? false : !u.showOptions;
diff --git a/libs/angular/src/vault/components/view.component.ts b/libs/angular/src/vault/components/view.component.ts
index c2666056705..3226e1292bb 100644
--- a/libs/angular/src/vault/components/view.component.ts
+++ b/libs/angular/src/vault/components/view.component.ts
@@ -60,6 +60,7 @@ export class ViewComponent implements OnDestroy, OnInit {
showPasswordCount: boolean;
showCardNumber: boolean;
showCardCode: boolean;
+ showPrivateKey: boolean;
canAccessPremium: boolean;
showPremiumRequiredTotp: boolean;
totpCode: string;
@@ -325,6 +326,10 @@ export class ViewComponent implements OnDestroy, OnInit {
}
}
+ togglePrivateKey() {
+ this.showPrivateKey = !this.showPrivateKey;
+ }
+
async checkPassword() {
if (
this.cipher.login == null ||
diff --git a/libs/common/src/admin-console/enums/provider-type.enum.ts b/libs/common/src/admin-console/enums/provider-type.enum.ts
index 5f81c338f0e..d802c659f6f 100644
--- a/libs/common/src/admin-console/enums/provider-type.enum.ts
+++ b/libs/common/src/admin-console/enums/provider-type.enum.ts
@@ -1,4 +1,5 @@
export enum ProviderType {
Msp = 0,
Reseller = 1,
+ MultiOrganizationEnterprise = 2,
}
diff --git a/libs/common/src/billing/models/response/provider-subscription-response.ts b/libs/common/src/billing/models/response/provider-subscription-response.ts
index 2dc9d4281de..2ecf988addd 100644
--- a/libs/common/src/billing/models/response/provider-subscription-response.ts
+++ b/libs/common/src/billing/models/response/provider-subscription-response.ts
@@ -1,3 +1,5 @@
+import { ProviderType } from "@bitwarden/common/admin-console/enums";
+import { PlanType, ProductTierType } from "@bitwarden/common/billing/enums";
import { SubscriptionSuspensionResponse } from "@bitwarden/common/billing/models/response/subscription-suspension.response";
import { TaxInfoResponse } from "@bitwarden/common/billing/models/response/tax-info.response";
@@ -13,6 +15,7 @@ export class ProviderSubscriptionResponse extends BaseResponse {
taxInformation?: TaxInfoResponse;
cancelAt?: string;
suspension?: SubscriptionSuspensionResponse;
+ providerType: ProviderType;
constructor(response: any) {
super(response);
@@ -34,6 +37,7 @@ export class ProviderSubscriptionResponse extends BaseResponse {
if (suspension != null) {
this.suspension = new SubscriptionSuspensionResponse(suspension);
}
+ this.providerType = this.getResponseProperty("providerType");
}
}
@@ -44,6 +48,8 @@ export class ProviderPlanResponse extends BaseResponse {
purchasedSeats: number;
cost: number;
cadence: string;
+ type: PlanType;
+ productTier: ProductTierType;
constructor(response: any) {
super(response);
@@ -53,5 +59,7 @@ export class ProviderPlanResponse extends BaseResponse {
this.purchasedSeats = this.getResponseProperty("PurchasedSeats");
this.cost = this.getResponseProperty("Cost");
this.cadence = this.getResponseProperty("Cadence");
+ this.type = this.getResponseProperty("Type");
+ this.productTier = this.getResponseProperty("ProductTier");
}
}
diff --git a/libs/common/src/enums/event-type.enum.ts b/libs/common/src/enums/event-type.enum.ts
index c72fb80de4d..51b324bb434 100644
--- a/libs/common/src/enums/event-type.enum.ts
+++ b/libs/common/src/enums/event-type.enum.ts
@@ -56,6 +56,8 @@ export enum EventType {
OrganizationUser_Restored = 1512,
OrganizationUser_ApprovedAuthRequest = 1513,
OrganizationUser_RejectedAuthRequest = 1514,
+ OrganizationUser_Deleted = 1515,
+ OrganizationUser_Left = 1516,
Organization_Updated = 1600,
Organization_PurgedVault = 1601,
diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts
index ea016e34350..47cdbc90cfd 100644
--- a/libs/common/src/enums/feature-flag.enum.ts
+++ b/libs/common/src/enums/feature-flag.enum.ts
@@ -27,6 +27,8 @@ export enum FeatureFlag {
EnableNewCardCombinedExpiryAutofill = "enable-new-card-combined-expiry-autofill",
DelayFido2PageScriptInitWithinMv2 = "delay-fido2-page-script-init-within-mv2",
AccountDeprovisioning = "pm-10308-account-deprovisioning",
+ SSHKeyVaultItem = "ssh-key-vault-item",
+ SSHAgent = "ssh-agent",
NotificationBarAddLoginImprovements = "notification-bar-add-login-improvements",
AC2476_DeprecateStripeSourcesAPI = "AC-2476-deprecate-stripe-sources-api",
CipherKeyEncryption = "cipher-key-encryption",
@@ -35,6 +37,7 @@ export enum FeatureFlag {
AccessIntelligence = "pm-13227-access-intelligence",
Pm13322AddPolicyDefinitions = "pm-13322-add-policy-definitions",
LimitCollectionCreationDeletionSplit = "pm-10863-limit-collection-creation-deletion-split",
+ CriticalApps = "pm-14466-risk-insights-critical-application",
}
export type AllowedFeatureFlagTypes = boolean | number | string;
@@ -72,6 +75,8 @@ export const DefaultFeatureFlagValue = {
[FeatureFlag.EnableNewCardCombinedExpiryAutofill]: FALSE,
[FeatureFlag.DelayFido2PageScriptInitWithinMv2]: FALSE,
[FeatureFlag.AccountDeprovisioning]: FALSE,
+ [FeatureFlag.SSHKeyVaultItem]: FALSE,
+ [FeatureFlag.SSHAgent]: FALSE,
[FeatureFlag.NotificationBarAddLoginImprovements]: FALSE,
[FeatureFlag.AC2476_DeprecateStripeSourcesAPI]: FALSE,
[FeatureFlag.CipherKeyEncryption]: FALSE,
@@ -80,6 +85,7 @@ export const DefaultFeatureFlagValue = {
[FeatureFlag.AccessIntelligence]: FALSE,
[FeatureFlag.Pm13322AddPolicyDefinitions]: FALSE,
[FeatureFlag.LimitCollectionCreationDeletionSplit]: FALSE,
+ [FeatureFlag.CriticalApps]: FALSE,
} satisfies Record;
export type DefaultFeatureFlagValueType = typeof DefaultFeatureFlagValue;
diff --git a/libs/common/src/models/export/cipher.export.ts b/libs/common/src/models/export/cipher.export.ts
index 64583f7fcef..432a2d4e250 100644
--- a/libs/common/src/models/export/cipher.export.ts
+++ b/libs/common/src/models/export/cipher.export.ts
@@ -10,6 +10,7 @@ import { IdentityExport } from "./identity.export";
import { LoginExport } from "./login.export";
import { PasswordHistoryExport } from "./password-history.export";
import { SecureNoteExport } from "./secure-note.export";
+import { SshKeyExport } from "./ssh-key.export";
import { safeGetString } from "./utils";
export class CipherExport {
@@ -27,6 +28,7 @@ export class CipherExport {
req.secureNote = null;
req.card = null;
req.identity = null;
+ req.sshKey = null;
req.reprompt = CipherRepromptType.None;
req.passwordHistory = [];
req.creationDate = null;
@@ -67,6 +69,8 @@ export class CipherExport {
case CipherType.Identity:
view.identity = IdentityExport.toView(req.identity);
break;
+ case CipherType.SshKey:
+ view.sshKey = SshKeyExport.toView(req.sshKey);
}
if (req.passwordHistory != null) {
@@ -108,6 +112,9 @@ export class CipherExport {
case CipherType.Identity:
domain.identity = IdentityExport.toDomain(req.identity);
break;
+ case CipherType.SshKey:
+ domain.sshKey = SshKeyExport.toDomain(req.sshKey);
+ break;
}
if (req.passwordHistory != null) {
@@ -132,6 +139,7 @@ export class CipherExport {
secureNote: SecureNoteExport;
card: CardExport;
identity: IdentityExport;
+ sshKey: SshKeyExport;
reprompt: CipherRepromptType;
passwordHistory: PasswordHistoryExport[] = null;
revisionDate: Date = null;
@@ -171,6 +179,9 @@ export class CipherExport {
case CipherType.Identity:
this.identity = new IdentityExport(o.identity);
break;
+ case CipherType.SshKey:
+ this.sshKey = new SshKeyExport(o.sshKey);
+ break;
}
if (o.passwordHistory != null) {
diff --git a/libs/common/src/models/export/ssh-key.export.ts b/libs/common/src/models/export/ssh-key.export.ts
new file mode 100644
index 00000000000..86683e97e20
--- /dev/null
+++ b/libs/common/src/models/export/ssh-key.export.ts
@@ -0,0 +1,44 @@
+import { SshKeyView as SshKeyView } from "@bitwarden/common/vault/models/view/ssh-key.view";
+
+import { EncString } from "../../platform/models/domain/enc-string";
+import { SshKey as SshKeyDomain } from "../../vault/models/domain/ssh-key";
+
+import { safeGetString } from "./utils";
+
+export class SshKeyExport {
+ static template(): SshKeyExport {
+ const req = new SshKeyExport();
+ req.privateKey = "";
+ req.publicKey = "";
+ req.keyFingerprint = "";
+ return req;
+ }
+
+ static toView(req: SshKeyExport, view = new SshKeyView()) {
+ view.privateKey = req.privateKey;
+ view.publicKey = req.publicKey;
+ view.keyFingerprint = req.keyFingerprint;
+ return view;
+ }
+
+ static toDomain(req: SshKeyExport, domain = new SshKeyDomain()) {
+ domain.privateKey = req.privateKey != null ? new EncString(req.privateKey) : null;
+ domain.publicKey = req.publicKey != null ? new EncString(req.publicKey) : null;
+ domain.keyFingerprint = req.keyFingerprint != null ? new EncString(req.keyFingerprint) : null;
+ return domain;
+ }
+
+ privateKey: string;
+ publicKey: string;
+ keyFingerprint: string;
+
+ constructor(o?: SshKeyView | SshKeyDomain) {
+ if (o == null) {
+ return;
+ }
+
+ this.privateKey = safeGetString(o.privateKey);
+ this.publicKey = safeGetString(o.publicKey);
+ this.keyFingerprint = safeGetString(o.keyFingerprint);
+ }
+}
diff --git a/libs/common/src/tools/state/user-state-subject.spec.ts b/libs/common/src/tools/state/user-state-subject.spec.ts
index 9f5475df9de..ee78a5c048b 100644
--- a/libs/common/src/tools/state/user-state-subject.spec.ts
+++ b/libs/common/src/tools/state/user-state-subject.spec.ts
@@ -373,7 +373,11 @@ describe("UserStateSubject", () => {
singleUserId$.next(SomeUser);
await awaitAsync();
- expect(state.nextMock).toHaveBeenCalledWith({ foo: "next" });
+ expect(state.nextMock).toHaveBeenCalledWith({
+ foo: "next",
+ // FIXME: don't leak this detail into the test
+ "$^$ALWAYS_UPDATE_KLUDGE_PROPERTY$^$": 0,
+ });
});
it("waits to evaluate `UserState.update` until singleUserEncryptor$ emits", async () => {
@@ -394,7 +398,13 @@ describe("UserStateSubject", () => {
await awaitAsync();
const encrypted = { foo: "encrypt(next)" };
- expect(state.nextMock).toHaveBeenCalledWith({ id: null, secret: encrypted, disclosed: null });
+ expect(state.nextMock).toHaveBeenCalledWith({
+ id: null,
+ secret: encrypted,
+ disclosed: null,
+ // FIXME: don't leak this detail into the test
+ "$^$ALWAYS_UPDATE_KLUDGE_PROPERTY$^$": 0,
+ });
});
it("applies dynamic constraints", async () => {
diff --git a/libs/common/src/tools/state/user-state-subject.ts b/libs/common/src/tools/state/user-state-subject.ts
index 845ab25c808..0b562cc7a1f 100644
--- a/libs/common/src/tools/state/user-state-subject.ts
+++ b/libs/common/src/tools/state/user-state-subject.ts
@@ -43,6 +43,23 @@ import { UserStateSubjectDependencies } from "./user-state-subject-dependencies"
type Constrained = { constraints: Readonly>; state: State };
+// FIXME: The subject should always repeat the value when it's own `next` method is called.
+//
+// Chrome StateService only calls `next` when the underlying values changes. When enforcing,
+// say, a minimum constraint, any value beneath the minimum becomes the minimum. This prevents
+// invalid data received in sequence from calling `next` because the state provider doesn't
+// emit.
+//
+// The hack is pretty simple. Insert arbitrary data into the saved data to ensure
+// that it *always* changes.
+//
+// Any real fix will be fairly complex because it needs to recognize *fast* when it
+// is waiting. Alternatively, the kludge could become a format properly fed by random noise.
+//
+// NOTE: this only matters for plaintext objects; encrypted fields change with every
+// update b/c their IVs change.
+const ALWAYS_UPDATE_KLUDGE = "$^$ALWAYS_UPDATE_KLUDGE_PROPERTY$^$";
+
/**
* Adapt a state provider to an rxjs subject.
*
@@ -420,8 +437,25 @@ export class UserStateSubject<
private inputSubscription: Unsubscribable;
private outputSubscription: Unsubscribable;
+ private counter = 0;
+
private onNext(value: unknown) {
- this.state.update(() => value).catch((e: any) => this.onError(e));
+ this.state
+ .update(() => {
+ if (typeof value === "object") {
+ // related: ALWAYS_UPDATE_KLUDGE FIXME
+ const counter = this.counter++;
+ if (counter > Number.MAX_SAFE_INTEGER) {
+ this.counter = 0;
+ }
+
+ const kludge = value as any;
+ kludge[ALWAYS_UPDATE_KLUDGE] = counter;
+ }
+
+ return value;
+ })
+ .catch((e: any) => this.onError(e));
}
private onError(value: any) {
diff --git a/libs/common/src/vault/enums/cipher-type.ts b/libs/common/src/vault/enums/cipher-type.ts
index cce7874d667..0b7bbf1ee17 100644
--- a/libs/common/src/vault/enums/cipher-type.ts
+++ b/libs/common/src/vault/enums/cipher-type.ts
@@ -3,4 +3,5 @@ export enum CipherType {
SecureNote = 2,
Card = 3,
Identity = 4,
+ SshKey = 5,
}
diff --git a/libs/common/src/vault/icon/build-cipher-icon.ts b/libs/common/src/vault/icon/build-cipher-icon.ts
index 9e6e671f44d..78e6ecd7b4f 100644
--- a/libs/common/src/vault/icon/build-cipher-icon.ts
+++ b/libs/common/src/vault/icon/build-cipher-icon.ts
@@ -67,6 +67,9 @@ export function buildCipherIcon(iconsServerUrl: string, cipher: CipherView, show
case CipherType.Identity:
icon = "bwi-id-card";
break;
+ case CipherType.SshKey:
+ icon = "bwi-key";
+ break;
default:
break;
}
diff --git a/libs/common/src/vault/models/api/ssh-key.api.ts b/libs/common/src/vault/models/api/ssh-key.api.ts
new file mode 100644
index 00000000000..e14f72bbc6a
--- /dev/null
+++ b/libs/common/src/vault/models/api/ssh-key.api.ts
@@ -0,0 +1,17 @@
+import { BaseResponse } from "../../../models/response/base.response";
+
+export class SshKeyApi extends BaseResponse {
+ privateKey: string;
+ publicKey: string;
+ keyFingerprint: string;
+
+ constructor(data: any = null) {
+ super(data);
+ if (data == null) {
+ return;
+ }
+ this.privateKey = this.getResponseProperty("PrivateKey");
+ this.publicKey = this.getResponseProperty("PublicKey");
+ this.keyFingerprint = this.getResponseProperty("KeyFingerprint");
+ }
+}
diff --git a/libs/common/src/vault/models/data/cipher.data.ts b/libs/common/src/vault/models/data/cipher.data.ts
index f8db7186d61..476c651f3ae 100644
--- a/libs/common/src/vault/models/data/cipher.data.ts
+++ b/libs/common/src/vault/models/data/cipher.data.ts
@@ -11,6 +11,7 @@ import { IdentityData } from "./identity.data";
import { LoginData } from "./login.data";
import { PasswordHistoryData } from "./password-history.data";
import { SecureNoteData } from "./secure-note.data";
+import { SshKeyData } from "./ssh-key.data";
export class CipherData {
id: string;
@@ -28,6 +29,7 @@ export class CipherData {
secureNote?: SecureNoteData;
card?: CardData;
identity?: IdentityData;
+ sshKey?: SshKeyData;
fields?: FieldData[];
attachments?: AttachmentData[];
passwordHistory?: PasswordHistoryData[];
@@ -72,6 +74,9 @@ export class CipherData {
case CipherType.Identity:
this.identity = new IdentityData(response.identity);
break;
+ case CipherType.SshKey:
+ this.sshKey = new SshKeyData(response.sshKey);
+ break;
default:
break;
}
diff --git a/libs/common/src/vault/models/data/ssh-key.data.ts b/libs/common/src/vault/models/data/ssh-key.data.ts
new file mode 100644
index 00000000000..32b6ec994f3
--- /dev/null
+++ b/libs/common/src/vault/models/data/ssh-key.data.ts
@@ -0,0 +1,17 @@
+import { SshKeyApi } from "../api/ssh-key.api";
+
+export class SshKeyData {
+ privateKey: string;
+ publicKey: string;
+ keyFingerprint: string;
+
+ constructor(data?: SshKeyApi) {
+ if (data == null) {
+ return;
+ }
+
+ this.privateKey = data.privateKey;
+ this.publicKey = data.publicKey;
+ this.keyFingerprint = data.keyFingerprint;
+ }
+}
diff --git a/libs/common/src/vault/models/domain/cipher.ts b/libs/common/src/vault/models/domain/cipher.ts
index 475c9337525..79536f5379a 100644
--- a/libs/common/src/vault/models/domain/cipher.ts
+++ b/libs/common/src/vault/models/domain/cipher.ts
@@ -19,6 +19,7 @@ import { Identity } from "./identity";
import { Login } from "./login";
import { Password } from "./password";
import { SecureNote } from "./secure-note";
+import { SshKey } from "./ssh-key";
export class Cipher extends Domain implements Decryptable {
readonly initializerKey = InitializerKey.Cipher;
@@ -39,6 +40,7 @@ export class Cipher extends Domain implements Decryptable {
identity: Identity;
card: Card;
secureNote: SecureNote;
+ sshKey: SshKey;
attachments: Attachment[];
fields: Field[];
passwordHistory: Password[];
@@ -97,6 +99,9 @@ export class Cipher extends Domain implements Decryptable {
case CipherType.Identity:
this.identity = new Identity(obj.identity);
break;
+ case CipherType.SshKey:
+ this.sshKey = new SshKey(obj.sshKey);
+ break;
default:
break;
}
@@ -156,6 +161,9 @@ export class Cipher extends Domain implements Decryptable {
case CipherType.Identity:
model.identity = await this.identity.decrypt(this.organizationId, encKey);
break;
+ case CipherType.SshKey:
+ model.sshKey = await this.sshKey.decrypt(this.organizationId, encKey);
+ break;
default:
break;
}
@@ -240,6 +248,9 @@ export class Cipher extends Domain implements Decryptable {
case CipherType.Identity:
c.identity = this.identity.toIdentityData();
break;
+ case CipherType.SshKey:
+ c.sshKey = this.sshKey.toSshKeyData();
+ break;
default:
break;
}
@@ -295,6 +306,9 @@ export class Cipher extends Domain implements Decryptable {
case CipherType.SecureNote:
domain.secureNote = SecureNote.fromJSON(obj.secureNote);
break;
+ case CipherType.SshKey:
+ domain.sshKey = SshKey.fromJSON(obj.sshKey);
+ break;
default:
break;
}
diff --git a/libs/common/src/vault/models/domain/ssh-key.spec.ts b/libs/common/src/vault/models/domain/ssh-key.spec.ts
new file mode 100644
index 00000000000..f56d738fde8
--- /dev/null
+++ b/libs/common/src/vault/models/domain/ssh-key.spec.ts
@@ -0,0 +1,67 @@
+import { mockEnc } from "../../../../spec";
+import { SshKeyApi } from "../api/ssh-key.api";
+import { SshKeyData } from "../data/ssh-key.data";
+
+import { SshKey } from "./ssh-key";
+
+describe("Sshkey", () => {
+ let data: SshKeyData;
+
+ beforeEach(() => {
+ data = new SshKeyData(
+ new SshKeyApi({
+ PrivateKey: "privateKey",
+ PublicKey: "publicKey",
+ KeyFingerprint: "keyFingerprint",
+ }),
+ );
+ });
+
+ it("Convert", () => {
+ const sshKey = new SshKey(data);
+
+ expect(sshKey).toEqual({
+ privateKey: { encryptedString: "privateKey", encryptionType: 0 },
+ publicKey: { encryptedString: "publicKey", encryptionType: 0 },
+ keyFingerprint: { encryptedString: "keyFingerprint", encryptionType: 0 },
+ });
+ });
+
+ it("Convert from empty", () => {
+ const data = new SshKeyData();
+ const sshKey = new SshKey(data);
+
+ expect(sshKey).toEqual({
+ privateKey: null,
+ publicKey: null,
+ keyFingerprint: null,
+ });
+ });
+
+ it("toSshKeyData", () => {
+ const sshKey = new SshKey(data);
+ expect(sshKey.toSshKeyData()).toEqual(data);
+ });
+
+ it("Decrypt", async () => {
+ const sshKey = Object.assign(new SshKey(), {
+ privateKey: mockEnc("privateKey"),
+ publicKey: mockEnc("publicKey"),
+ keyFingerprint: mockEnc("keyFingerprint"),
+ });
+ const expectedView = {
+ privateKey: "privateKey",
+ publicKey: "publicKey",
+ keyFingerprint: "keyFingerprint",
+ };
+
+ const loginView = await sshKey.decrypt(null);
+ expect(loginView).toEqual(expectedView);
+ });
+
+ describe("fromJSON", () => {
+ it("returns null if object is null", () => {
+ expect(SshKey.fromJSON(null)).toBeNull();
+ });
+ });
+});
diff --git a/libs/common/src/vault/models/domain/ssh-key.ts b/libs/common/src/vault/models/domain/ssh-key.ts
new file mode 100644
index 00000000000..e7c24b45ba8
--- /dev/null
+++ b/libs/common/src/vault/models/domain/ssh-key.ts
@@ -0,0 +1,70 @@
+import { Jsonify } from "type-fest";
+
+import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
+
+import Domain from "../../../platform/models/domain/domain-base";
+import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
+import { SshKeyData } from "../data/ssh-key.data";
+import { SshKeyView } from "../view/ssh-key.view";
+
+export class SshKey extends Domain {
+ privateKey: EncString;
+ publicKey: EncString;
+ keyFingerprint: EncString;
+
+ constructor(obj?: SshKeyData) {
+ super();
+ if (obj == null) {
+ return;
+ }
+
+ this.buildDomainModel(
+ this,
+ obj,
+ {
+ privateKey: null,
+ publicKey: null,
+ keyFingerprint: null,
+ },
+ [],
+ );
+ }
+
+ decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise {
+ return this.decryptObj(
+ new SshKeyView(),
+ {
+ privateKey: null,
+ publicKey: null,
+ keyFingerprint: null,
+ },
+ orgId,
+ encKey,
+ );
+ }
+
+ toSshKeyData(): SshKeyData {
+ const c = new SshKeyData();
+ this.buildDataModel(this, c, {
+ privateKey: null,
+ publicKey: null,
+ keyFingerprint: null,
+ });
+ return c;
+ }
+
+ static fromJSON(obj: Partial>): SshKey {
+ if (obj == null) {
+ return null;
+ }
+
+ const privateKey = EncString.fromJSON(obj.privateKey);
+ const publicKey = EncString.fromJSON(obj.publicKey);
+ const keyFingerprint = EncString.fromJSON(obj.keyFingerprint);
+ return Object.assign(new SshKey(), obj, {
+ privateKey,
+ publicKey,
+ keyFingerprint,
+ });
+ }
+}
diff --git a/libs/common/src/vault/models/request/cipher.request.ts b/libs/common/src/vault/models/request/cipher.request.ts
index 52a55b6c3e4..f24254f7432 100644
--- a/libs/common/src/vault/models/request/cipher.request.ts
+++ b/libs/common/src/vault/models/request/cipher.request.ts
@@ -7,6 +7,7 @@ import { IdentityApi } from "../api/identity.api";
import { LoginUriApi } from "../api/login-uri.api";
import { LoginApi } from "../api/login.api";
import { SecureNoteApi } from "../api/secure-note.api";
+import { SshKeyApi } from "../api/ssh-key.api";
import { Cipher } from "../domain/cipher";
import { AttachmentRequest } from "./attachment.request";
@@ -23,6 +24,7 @@ export class CipherRequest {
secureNote: SecureNoteApi;
card: CardApi;
identity: IdentityApi;
+ sshKey: SshKeyApi;
fields: FieldApi[];
passwordHistory: PasswordHistoryRequest[];
// Deprecated, remove at some point and rename attachments2 to attachments
@@ -93,6 +95,17 @@ export class CipherRequest {
this.secureNote = new SecureNoteApi();
this.secureNote.type = cipher.secureNote.type;
break;
+ case CipherType.SshKey:
+ this.sshKey = new SshKeyApi();
+ this.sshKey.privateKey =
+ cipher.sshKey.privateKey != null ? cipher.sshKey.privateKey.encryptedString : null;
+ this.sshKey.publicKey =
+ cipher.sshKey.publicKey != null ? cipher.sshKey.publicKey.encryptedString : null;
+ this.sshKey.keyFingerprint =
+ cipher.sshKey.keyFingerprint != null
+ ? cipher.sshKey.keyFingerprint.encryptedString
+ : null;
+ break;
case CipherType.Card:
this.card = new CardApi();
this.card.cardholderName =
diff --git a/libs/common/src/vault/models/response/cipher.response.ts b/libs/common/src/vault/models/response/cipher.response.ts
index 67709b602e3..7e2805b7510 100644
--- a/libs/common/src/vault/models/response/cipher.response.ts
+++ b/libs/common/src/vault/models/response/cipher.response.ts
@@ -5,6 +5,7 @@ import { FieldApi } from "../api/field.api";
import { IdentityApi } from "../api/identity.api";
import { LoginApi } from "../api/login.api";
import { SecureNoteApi } from "../api/secure-note.api";
+import { SshKeyApi } from "../api/ssh-key.api";
import { AttachmentResponse } from "./attachment.response";
import { PasswordHistoryResponse } from "./password-history.response";
@@ -21,6 +22,7 @@ export class CipherResponse extends BaseResponse {
card: CardApi;
identity: IdentityApi;
secureNote: SecureNoteApi;
+ sshKey: SshKeyApi;
favorite: boolean;
edit: boolean;
viewPassword: boolean;
@@ -75,6 +77,11 @@ export class CipherResponse extends BaseResponse {
this.secureNote = new SecureNoteApi(secureNote);
}
+ const sshKey = this.getResponseProperty("sshKey");
+ if (sshKey != null) {
+ this.sshKey = new SshKeyApi(sshKey);
+ }
+
const fields = this.getResponseProperty("Fields");
if (fields != null) {
this.fields = fields.map((f: any) => new FieldApi(f));
diff --git a/libs/common/src/vault/models/view/cipher.view.ts b/libs/common/src/vault/models/view/cipher.view.ts
index 3ea3f109be1..4d429bb390f 100644
--- a/libs/common/src/vault/models/view/cipher.view.ts
+++ b/libs/common/src/vault/models/view/cipher.view.ts
@@ -14,6 +14,7 @@ import { IdentityView } from "./identity.view";
import { LoginView } from "./login.view";
import { PasswordHistoryView } from "./password-history.view";
import { SecureNoteView } from "./secure-note.view";
+import { SshKeyView } from "./ssh-key.view";
export class CipherView implements View, InitializerMetadata {
readonly initializerKey = InitializerKey.CipherView;
@@ -33,6 +34,7 @@ export class CipherView implements View, InitializerMetadata {
identity = new IdentityView();
card = new CardView();
secureNote = new SecureNoteView();
+ sshKey = new SshKeyView();
attachments: AttachmentView[] = null;
fields: FieldView[] = null;
passwordHistory: PasswordHistoryView[] = null;
@@ -74,6 +76,8 @@ export class CipherView implements View, InitializerMetadata {
return this.card;
case CipherType.Identity:
return this.identity;
+ case CipherType.SshKey:
+ return this.sshKey;
default:
break;
}
@@ -190,6 +194,9 @@ export class CipherView implements View, InitializerMetadata {
case CipherType.SecureNote:
view.secureNote = SecureNoteView.fromJSON(obj.secureNote);
break;
+ case CipherType.SshKey:
+ view.sshKey = SshKeyView.fromJSON(obj.sshKey);
+ break;
default:
break;
}
diff --git a/libs/common/src/vault/models/view/ssh-key.view.ts b/libs/common/src/vault/models/view/ssh-key.view.ts
new file mode 100644
index 00000000000..4fedb1f8a36
--- /dev/null
+++ b/libs/common/src/vault/models/view/ssh-key.view.ts
@@ -0,0 +1,41 @@
+import { Jsonify } from "type-fest";
+
+import { SshKey } from "../domain/ssh-key";
+
+import { ItemView } from "./item.view";
+
+export class SshKeyView extends ItemView {
+ privateKey: string = null;
+ publicKey: string = null;
+ keyFingerprint: string = null;
+
+ constructor(n?: SshKey) {
+ super();
+ if (!n) {
+ return;
+ }
+ }
+
+ get maskedPrivateKey(): string {
+ let lines = this.privateKey.split("\n").filter((l) => l.trim() !== "");
+ lines = lines.map((l, i) => {
+ if (i === 0 || i === lines.length - 1) {
+ return l;
+ }
+ return this.maskLine(l);
+ });
+ return lines.join("\n");
+ }
+
+ private maskLine(line: string): string {
+ return "•".repeat(32);
+ }
+
+ get subTitle(): string {
+ return this.keyFingerprint;
+ }
+
+ static fromJSON(obj: Partial>): SshKeyView {
+ return Object.assign(new SshKeyView(), obj);
+ }
+}
diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts
index 6b618e25502..474976932e8 100644
--- a/libs/common/src/vault/services/cipher.service.ts
+++ b/libs/common/src/vault/services/cipher.service.ts
@@ -54,6 +54,7 @@ import { LoginUri } from "../models/domain/login-uri";
import { Password } from "../models/domain/password";
import { SecureNote } from "../models/domain/secure-note";
import { SortedCiphersCache } from "../models/domain/sorted-ciphers-cache";
+import { SshKey } from "../models/domain/ssh-key";
import { CipherBulkDeleteRequest } from "../models/request/cipher-bulk-delete.request";
import { CipherBulkMoveRequest } from "../models/request/cipher-bulk-move.request";
import { CipherBulkRestoreRequest } from "../models/request/cipher-bulk-restore.request";
@@ -1570,6 +1571,19 @@ export class CipherService implements CipherServiceAbstraction {
key,
);
return;
+ case CipherType.SshKey:
+ cipher.sshKey = new SshKey();
+ await this.encryptObjProperty(
+ model.sshKey,
+ cipher.sshKey,
+ {
+ privateKey: null,
+ publicKey: null,
+ keyFingerprint: null,
+ },
+ key,
+ );
+ return;
default:
throw new Error("Unknown cipher type.");
}
diff --git a/libs/importer/src/components/dialog/import-success-dialog.component.ts b/libs/importer/src/components/dialog/import-success-dialog.component.ts
index 1e0d4f7fda9..75e0754423e 100644
--- a/libs/importer/src/components/dialog/import-success-dialog.component.ts
+++ b/libs/importer/src/components/dialog/import-success-dialog.component.ts
@@ -38,6 +38,7 @@ export class ImportSuccessDialogComponent implements OnInit {
let cards = 0;
let identities = 0;
let secureNotes = 0;
+ let sshKeys = 0;
this.data.ciphers.map((c) => {
switch (c.type) {
case CipherType.Login:
@@ -52,6 +53,9 @@ export class ImportSuccessDialogComponent implements OnInit {
case CipherType.Identity:
identities++;
break;
+ case CipherType.SshKey:
+ sshKeys++;
+ break;
default:
break;
}
@@ -70,6 +74,9 @@ export class ImportSuccessDialogComponent implements OnInit {
if (secureNotes > 0) {
list.push({ icon: "sticky-note", type: "typeSecureNote", count: secureNotes });
}
+ if (sshKeys > 0) {
+ list.push({ icon: "key", type: "typeSSHKey", count: sshKeys });
+ }
if (this.data.folders.length > 0) {
list.push({ icon: "folder", type: "folders", count: this.data.folders.length });
}
diff --git a/libs/tools/generator/core/src/services/credential-generator.service.spec.ts b/libs/tools/generator/core/src/services/credential-generator.service.spec.ts
index bd26642157e..b6b43073431 100644
--- a/libs/tools/generator/core/src/services/credential-generator.service.spec.ts
+++ b/libs/tools/generator/core/src/services/credential-generator.service.spec.ts
@@ -1163,7 +1163,11 @@ describe("CredentialGeneratorService", () => {
await awaitAsync();
const result = await firstValueFrom(stateProvider.getUserState$(SettingsKey, SomeUser));
- expect(result).toEqual({ foo: "next value" });
+ expect(result).toEqual({
+ foo: "next value",
+ // FIXME: don't leak this detail into the test
+ "$^$ALWAYS_UPDATE_KLUDGE_PROPERTY$^$": 0,
+ });
});
it("waits for the user to become available", async () => {
diff --git a/libs/vault/src/cipher-form/cipher-form-container.ts b/libs/vault/src/cipher-form/cipher-form-container.ts
index 87495df643f..8010cf260df 100644
--- a/libs/vault/src/cipher-form/cipher-form-container.ts
+++ b/libs/vault/src/cipher-form/cipher-form-container.ts
@@ -8,6 +8,7 @@ import { CustomFieldsComponent } from "./components/custom-fields/custom-fields.
import { IdentitySectionComponent } from "./components/identity/identity.component";
import { ItemDetailsSectionComponent } from "./components/item-details/item-details-section.component";
import { LoginDetailsSectionComponent } from "./components/login-details-section/login-details-section.component";
+import { SshKeySectionComponent } from "./components/sshkey-section/sshkey-section.component";
/**
* The complete form for a cipher. Includes all the sub-forms from their respective section components.
@@ -20,6 +21,7 @@ export type CipherForm = {
autoFillOptions?: AutofillOptionsComponent["autofillOptionsForm"];
cardDetails?: CardDetailsSectionComponent["cardDetailsForm"];
identityDetails?: IdentitySectionComponent["identityForm"];
+ sshKeyDetails?: SshKeySectionComponent["sshKeyForm"];
customFields?: CustomFieldsComponent["customFieldsForm"];
};
diff --git a/libs/vault/src/cipher-form/components/cipher-form.component.html b/libs/vault/src/cipher-form/components/cipher-form.component.html
index 60dbd91fc36..2644741385b 100644
--- a/libs/vault/src/cipher-form/components/cipher-form.component.html
+++ b/libs/vault/src/cipher-form/components/cipher-form.component.html
@@ -22,6 +22,12 @@
[disabled]="config.mode === 'partial-edit'"
>
+
+
diff --git a/libs/vault/src/cipher-form/components/cipher-form.component.ts b/libs/vault/src/cipher-form/components/cipher-form.component.ts
index 4df6aa67ea6..d1bbbef0910 100644
--- a/libs/vault/src/cipher-form/components/cipher-form.component.ts
+++ b/libs/vault/src/cipher-form/components/cipher-form.component.ts
@@ -42,6 +42,7 @@ import { CardDetailsSectionComponent } from "./card-details-section/card-details
import { IdentitySectionComponent } from "./identity/identity.component";
import { ItemDetailsSectionComponent } from "./item-details/item-details-section.component";
import { LoginDetailsSectionComponent } from "./login-details-section/login-details-section.component";
+import { SshKeySectionComponent } from "./sshkey-section/sshkey-section.component";
@Component({
selector: "vault-cipher-form",
@@ -65,6 +66,7 @@ import { LoginDetailsSectionComponent } from "./login-details-section/login-deta
ItemDetailsSectionComponent,
CardDetailsSectionComponent,
IdentitySectionComponent,
+ SshKeySectionComponent,
NgIf,
AdditionalOptionsSectionComponent,
LoginDetailsSectionComponent,
diff --git a/libs/vault/src/cipher-form/components/sshkey-section/sshkey-section.component.html b/libs/vault/src/cipher-form/components/sshkey-section/sshkey-section.component.html
new file mode 100644
index 00000000000..51b07a1cbf3
--- /dev/null
+++ b/libs/vault/src/cipher-form/components/sshkey-section/sshkey-section.component.html
@@ -0,0 +1,30 @@
+
+
+
+ {{ "typeSshKey" | i18n }}
+
+
+
+
+ {{ "sshPrivateKey" | i18n }}
+
+
+
+
+
+ {{ "sshPublicKey" | i18n }}
+
+
+
+
+ {{ "sshFingerprint" | i18n }}
+
+
+
+
diff --git a/libs/vault/src/cipher-form/components/sshkey-section/sshkey-section.component.ts b/libs/vault/src/cipher-form/components/sshkey-section/sshkey-section.component.ts
new file mode 100644
index 00000000000..a15237421bd
--- /dev/null
+++ b/libs/vault/src/cipher-form/components/sshkey-section/sshkey-section.component.ts
@@ -0,0 +1,80 @@
+import { CommonModule } from "@angular/common";
+import { Component, Input, OnInit } from "@angular/core";
+import { FormBuilder, ReactiveFormsModule } from "@angular/forms";
+
+import { JslibModule } from "@bitwarden/angular/jslib.module";
+import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
+import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
+import {
+ CardComponent,
+ FormFieldModule,
+ IconButtonModule,
+ SectionComponent,
+ SectionHeaderComponent,
+ SelectModule,
+ TypographyModule,
+} from "@bitwarden/components";
+
+import { CipherFormContainer } from "../../cipher-form-container";
+
+@Component({
+ selector: "vault-sshkey-section",
+ templateUrl: "./sshkey-section.component.html",
+ standalone: true,
+ imports: [
+ CardComponent,
+ SectionComponent,
+ TypographyModule,
+ FormFieldModule,
+ ReactiveFormsModule,
+ SelectModule,
+ SectionHeaderComponent,
+ IconButtonModule,
+ JslibModule,
+ CommonModule,
+ ],
+})
+export class SshKeySectionComponent implements OnInit {
+ /** The original cipher */
+ @Input() originalCipherView: CipherView;
+
+ /** True when all fields should be disabled */
+ @Input() disabled: boolean;
+
+ /**
+ * All form fields associated with the ssh key
+ *
+ * Note: `as` is used to assert the type of the form control,
+ * leaving as just null gets inferred as `unknown`
+ */
+ sshKeyForm = this.formBuilder.group({
+ privateKey: null as string | null,
+ publicKey: null as string | null,
+ keyFingerprint: null as string | null,
+ });
+
+ constructor(
+ private cipherFormContainer: CipherFormContainer,
+ private formBuilder: FormBuilder,
+ private i18nService: I18nService,
+ ) {}
+
+ ngOnInit() {
+ if (this.originalCipherView?.card) {
+ this.setInitialValues();
+ }
+
+ this.sshKeyForm.disable();
+ }
+
+ /** Set form initial form values from the current cipher */
+ private setInitialValues() {
+ const { privateKey, publicKey, keyFingerprint } = this.originalCipherView.sshKey;
+
+ this.sshKeyForm.setValue({
+ privateKey,
+ publicKey,
+ keyFingerprint,
+ });
+ }
+}
diff --git a/libs/vault/src/cipher-view/cipher-view.component.html b/libs/vault/src/cipher-view/cipher-view.component.html
index ad5191b0e2b..f0ebeecdf40 100644
--- a/libs/vault/src/cipher-view/cipher-view.component.html
+++ b/libs/vault/src/cipher-view/cipher-view.component.html
@@ -40,6 +40,9 @@
+
+
+
diff --git a/libs/vault/src/cipher-view/cipher-view.component.ts b/libs/vault/src/cipher-view/cipher-view.component.ts
index 0871fd8e788..597be3283e6 100644
--- a/libs/vault/src/cipher-view/cipher-view.component.ts
+++ b/libs/vault/src/cipher-view/cipher-view.component.ts
@@ -21,6 +21,7 @@ import { CustomFieldV2Component } from "./custom-fields/custom-fields-v2.compone
import { ItemDetailsV2Component } from "./item-details/item-details-v2.component";
import { ItemHistoryV2Component } from "./item-history/item-history-v2.component";
import { LoginCredentialsViewComponent } from "./login-credentials/login-credentials-view.component";
+import { SshKeyViewComponent } from "./sshkey-sections/sshkey-view.component";
import { ViewIdentitySectionsComponent } from "./view-identity-sections/view-identity-sections.component";
@Component({
@@ -38,6 +39,7 @@ import { ViewIdentitySectionsComponent } from "./view-identity-sections/view-ide
ItemHistoryV2Component,
CustomFieldV2Component,
CardDetailsComponent,
+ SshKeyViewComponent,
ViewIdentitySectionsComponent,
LoginCredentialsViewComponent,
AutofillOptionsViewComponent,
@@ -95,6 +97,10 @@ export class CipherViewComponent implements OnChanges, OnDestroy {
return this.cipher.login?.uris.length > 0;
}
+ get hasSshKey() {
+ return this.cipher.sshKey?.privateKey;
+ }
+
async loadCipherData() {
// Load collections if not provided and the cipher has collectionIds
if (
diff --git a/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.html b/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.html
new file mode 100644
index 00000000000..ee5a94249c4
--- /dev/null
+++ b/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.html
@@ -0,0 +1,50 @@
+
+
+ {{ "typeSshKey" | i18n }}
+
+
+
+ {{ "sshPrivateKey" | i18n }}
+
+
+
+
+
+ {{ "sshPublicKey" | i18n }}
+
+
+
+
+ {{ "sshFingerprint" | i18n }}
+
+
+
+
+
diff --git a/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.ts b/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.ts
new file mode 100644
index 00000000000..7f553dbe58b
--- /dev/null
+++ b/libs/vault/src/cipher-view/sshkey-sections/sshkey-view.component.ts
@@ -0,0 +1,35 @@
+import { CommonModule } from "@angular/common";
+import { Component, Input } from "@angular/core";
+
+import { JslibModule } from "@bitwarden/angular/jslib.module";
+import { SshKeyView } from "@bitwarden/common/vault/models/view/ssh-key.view";
+import {
+ CardComponent,
+ SectionComponent,
+ SectionHeaderComponent,
+ TypographyModule,
+ FormFieldModule,
+ IconButtonModule,
+} from "@bitwarden/components";
+
+import { OrgIconDirective } from "../../components/org-icon.directive";
+
+@Component({
+ selector: "app-sshkey-view",
+ templateUrl: "sshkey-view.component.html",
+ standalone: true,
+ imports: [
+ CommonModule,
+ JslibModule,
+ CardComponent,
+ SectionComponent,
+ SectionHeaderComponent,
+ TypographyModule,
+ OrgIconDirective,
+ FormFieldModule,
+ IconButtonModule,
+ ],
+})
+export class SshKeyViewComponent {
+ @Input() sshKey: SshKeyView;
+}
diff --git a/libs/vault/src/components/copy-cipher-field.directive.ts b/libs/vault/src/components/copy-cipher-field.directive.ts
index 7d842c36bfe..e03419815bf 100644
--- a/libs/vault/src/components/copy-cipher-field.directive.ts
+++ b/libs/vault/src/components/copy-cipher-field.directive.ts
@@ -91,6 +91,12 @@ export class CopyCipherFieldDirective implements OnChanges {
return this.cipher.identity?.fullAddressForCopy;
case "secureNote":
return this.cipher.notes;
+ case "privateKey":
+ return this.cipher.sshKey?.privateKey;
+ case "publicKey":
+ return this.cipher.sshKey?.publicKey;
+ case "keyFingerprint":
+ return this.cipher.sshKey?.keyFingerprint;
default:
return null;
}
diff --git a/libs/vault/src/components/password-history-view/password-history-view.component.html b/libs/vault/src/components/password-history-view/password-history-view.component.html
index 44b7fea5f75..459c679945c 100644
--- a/libs/vault/src/components/password-history-view/password-history-view.component.html
+++ b/libs/vault/src/components/password-history-view/password-history-view.component.html
@@ -15,10 +15,10 @@
bitIconButton="bwi-clone"
[appA11yTitle]="'copyPassword' | i18n"
appStopClick
- (click)="copy(h.password)"
- >
-
-
+ [appCopyClick]="h.password"
+ [valueLabel]="'password' | i18n"
+ showToast
+ >
diff --git a/libs/vault/src/components/password-history-view/password-history-view.component.spec.ts b/libs/vault/src/components/password-history-view/password-history-view.component.spec.ts
index 8772a245821..3900681f230 100644
--- a/libs/vault/src/components/password-history-view/password-history-view.component.spec.ts
+++ b/libs/vault/src/components/password-history-view/password-history-view.component.spec.ts
@@ -3,14 +3,13 @@ import { By } from "@angular/platform-browser";
import { BehaviorSubject } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
-import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.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 { ColorPasswordModule, ItemModule, ToastService } from "@bitwarden/components";
+import { ColorPasswordModule, ItemModule } from "@bitwarden/components";
import { ColorPasswordComponent } from "@bitwarden/components/src/color-password/color-password.component";
import { PasswordHistoryViewComponent } from "./password-history-view.component";
@@ -25,8 +24,6 @@ describe("PasswordHistoryViewComponent", () => {
organizationId: "222-444-555",
} as CipherView;
- const copyToClipboard = jest.fn();
- const showToast = jest.fn();
const activeAccount$ = new BehaviorSubject<{ id: string }>({ id: "666-444-444" });
const mockCipherService = {
get: jest.fn().mockResolvedValue({ decrypt: jest.fn().mockResolvedValue(mockCipher) }),
@@ -36,17 +33,13 @@ describe("PasswordHistoryViewComponent", () => {
beforeEach(async () => {
mockCipherService.get.mockClear();
mockCipherService.getKeyForCipherKeyDecryption.mockClear();
- copyToClipboard.mockClear();
- showToast.mockClear();
await TestBed.configureTestingModule({
imports: [ItemModule, ColorPasswordModule, JslibModule],
providers: [
- { provide: WINDOW, useValue: window },
{ provide: CipherService, useValue: mockCipherService },
- { provide: PlatformUtilsService, useValue: { copyToClipboard } },
+ { provide: PlatformUtilsService },
{ provide: AccountService, useValue: { activeAccount$ } },
- { provide: ToastService, useValue: { showToast } },
{ provide: I18nService, useValue: { t: (key: string) => key } },
],
}).compileComponents();
@@ -80,18 +73,5 @@ describe("PasswordHistoryViewComponent", () => {
"bad-password-2",
]);
});
-
- it("copies a password", () => {
- const copyButton = fixture.debugElement.query(By.css("button"));
-
- copyButton.nativeElement.click();
-
- expect(copyToClipboard).toHaveBeenCalledWith("bad-password-1", { window: window });
- expect(showToast).toHaveBeenCalledWith({
- message: "passwordCopied",
- title: "",
- variant: "info",
- });
- });
});
});
diff --git a/libs/vault/src/components/password-history-view/password-history-view.component.ts b/libs/vault/src/components/password-history-view/password-history-view.component.ts
index 5e858af7275..a0f0aa6b35b 100644
--- a/libs/vault/src/components/password-history-view/password-history-view.component.ts
+++ b/libs/vault/src/components/password-history-view/password-history-view.component.ts
@@ -1,21 +1,14 @@
import { CommonModule } from "@angular/common";
-import { OnInit, Inject, Component, Input } from "@angular/core";
+import { OnInit, Component, Input } from "@angular/core";
import { firstValueFrom, map } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
-import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
-import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { CipherId, UserId } from "@bitwarden/common/types/guid";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { PasswordHistoryView } from "@bitwarden/common/vault/models/view/password-history.view";
-import {
- ToastService,
- ItemModule,
- ColorPasswordModule,
- IconButtonModule,
-} from "@bitwarden/components";
+import { ItemModule, ColorPasswordModule, IconButtonModule } from "@bitwarden/components";
@Component({
selector: "vault-password-history-view",
@@ -33,29 +26,15 @@ export class PasswordHistoryViewComponent implements OnInit {
history: PasswordHistoryView[] = [];
constructor(
- @Inject(WINDOW) private win: Window,
protected cipherService: CipherService,
- protected platformUtilsService: PlatformUtilsService,
protected i18nService: I18nService,
protected accountService: AccountService,
- protected toastService: ToastService,
) {}
async ngOnInit() {
await this.init();
}
- /** Copies a password to the clipboard. */
- copy(password: string) {
- const copyOptions = this.win != null ? { window: this.win } : undefined;
- this.platformUtilsService.copyToClipboard(password, copyOptions);
- this.toastService.showToast({
- variant: "info",
- title: "",
- message: this.i18nService.t("passwordCopied"),
- });
- }
-
/** Retrieve the password history for the given cipher */
protected async init() {
const cipher = await this.cipherService.get(this.cipherId);
diff --git a/libs/vault/src/services/copy-cipher-field.service.ts b/libs/vault/src/services/copy-cipher-field.service.ts
index 4767ae01bca..1867b10cd17 100644
--- a/libs/vault/src/services/copy-cipher-field.service.ts
+++ b/libs/vault/src/services/copy-cipher-field.service.ts
@@ -25,7 +25,10 @@ export type CopyAction =
| "phone"
| "address"
| "secureNote"
- | "hiddenField";
+ | "hiddenField"
+ | "privateKey"
+ | "publicKey"
+ | "keyFingerprint";
type CopyActionInfo = {
/**
@@ -62,6 +65,9 @@ const CopyActions: Record = {
phone: { typeI18nKey: "phone", protected: true },
address: { typeI18nKey: "address", protected: true },
secureNote: { typeI18nKey: "note", protected: true },
+ privateKey: { typeI18nKey: "sshPrivateKey", protected: true },
+ publicKey: { typeI18nKey: "sshPublicKey", protected: true },
+ keyFingerprint: { typeI18nKey: "sshFingerprint", protected: true },
hiddenField: {
typeI18nKey: "value",
protected: true,
diff --git a/package-lock.json b/package-lock.json
index ab1c7d90655..1ba38d10dc8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -40,7 +40,7 @@
"bufferutil": "4.0.8",
"chalk": "4.1.2",
"commander": "11.1.0",
- "core-js": "3.36.1",
+ "core-js": "3.39.0",
"form-data": "4.0.0",
"https-proxy-agent": "7.0.5",
"inquirer": "8.2.6",
@@ -134,7 +134,7 @@
"css-loader": "7.1.2",
"electron": "32.1.1",
"electron-builder": "24.13.3",
- "electron-log": "5.0.1",
+ "electron-log": "5.2.2",
"electron-reload": "2.0.0-alpha.1",
"electron-store": "8.2.0",
"electron-updater": "6.3.9",
@@ -151,7 +151,7 @@
"gulp-json-editor": "2.6.0",
"gulp-replace": "1.1.4",
"gulp-zip": "6.0.0",
- "html-loader": "5.0.0",
+ "html-loader": "5.1.0",
"html-webpack-injector": "1.1.4",
"html-webpack-plugin": "5.6.3",
"husky": "9.1.4",
@@ -162,7 +162,7 @@
"lint-staged": "15.2.8",
"mini-css-extract-plugin": "2.9.1",
"node-ipc": "9.2.1",
- "postcss": "8.4.38",
+ "postcss": "8.4.47",
"postcss-loader": "8.1.1",
"prettier": "3.3.3",
"prettier-plugin-tailwindcss": "0.6.8",
@@ -182,7 +182,7 @@
"url": "0.11.4",
"util": "0.12.5",
"wait-on": "8.0.1",
- "webpack": "5.94.0",
+ "webpack": "5.96.1",
"webpack-cli": "5.1.4",
"webpack-dev-server": "5.0.4",
"webpack-node-externals": "3.0.0"
@@ -924,6 +924,30 @@
}
}
},
+ "node_modules/@angular-devkit/build-angular/node_modules/eslint-scope": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
+ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^4.1.1"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/@angular-devkit/build-angular/node_modules/estraverse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
"node_modules/@angular-devkit/build-angular/node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@@ -1030,6 +1054,13 @@
"node": ">= 10"
}
},
+ "node_modules/@angular-devkit/build-angular/node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@angular-devkit/build-angular/node_modules/mini-css-extract-plugin": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.8.1.tgz",
@@ -1226,6 +1257,53 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/@angular-devkit/build-angular/node_modules/webpack": {
+ "version": "5.94.0",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz",
+ "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.5",
+ "@webassemblyjs/ast": "^1.12.1",
+ "@webassemblyjs/wasm-edit": "^1.12.1",
+ "@webassemblyjs/wasm-parser": "^1.12.1",
+ "acorn": "^8.7.1",
+ "acorn-import-attributes": "^1.9.5",
+ "browserslist": "^4.21.10",
+ "chrome-trace-event": "^1.0.2",
+ "enhanced-resolve": "^5.17.1",
+ "es-module-lexer": "^1.2.1",
+ "eslint-scope": "5.1.1",
+ "events": "^3.2.0",
+ "glob-to-regexp": "^0.4.1",
+ "graceful-fs": "^4.2.11",
+ "json-parse-even-better-errors": "^2.3.1",
+ "loader-runner": "^4.2.0",
+ "mime-types": "^2.1.27",
+ "neo-async": "^2.6.2",
+ "schema-utils": "^3.2.0",
+ "tapable": "^2.1.1",
+ "terser-webpack-plugin": "^5.3.10",
+ "watchpack": "^2.4.1",
+ "webpack-sources": "^3.2.3"
+ },
+ "bin": {
+ "webpack": "bin/webpack.js"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependenciesMeta": {
+ "webpack-cli": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@angular-devkit/build-angular/node_modules/webpack-dev-server": {
"version": "4.15.1",
"resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz",
@@ -1310,6 +1388,73 @@
"webpack": "^4.0.0 || ^5.0.0"
}
},
+ "node_modules/@angular-devkit/build-angular/node_modules/webpack/node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/@angular-devkit/build-angular/node_modules/webpack/node_modules/ajv-keywords": {
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
+ "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "ajv": "^6.9.1"
+ }
+ },
+ "node_modules/@angular-devkit/build-angular/node_modules/webpack/node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@angular-devkit/build-angular/node_modules/webpack/node_modules/schema-utils": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
+ "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/json-schema": "^7.0.8",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/@angular-devkit/build-angular/node_modules/webpack/node_modules/watchpack": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz",
+ "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "glob-to-regexp": "^0.4.1",
+ "graceful-fs": "^4.1.2"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
"node_modules/@angular-devkit/core": {
"version": "18.2.11",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.11.tgz",
@@ -9203,6 +9348,28 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/eslint": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
+ "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "*",
+ "@types/json-schema": "*"
+ }
+ },
+ "node_modules/@types/eslint-scope": {
+ "version": "3.7.7",
+ "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz",
+ "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/eslint": "*",
+ "@types/estree": "*"
+ }
+ },
"node_modules/@types/estree": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
@@ -14735,9 +14902,9 @@
}
},
"node_modules/core-js": {
- "version": "3.36.1",
- "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.36.1.tgz",
- "integrity": "sha512-BTvUrwxVBezj5SZ3f10ImnX2oRByMxql3EimVqMysepbC9EeMUOpLwdy6Eoili2x6E4kf+ZUB5k/+Jv55alPfA==",
+ "version": "3.39.0",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.39.0.tgz",
+ "integrity": "sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g==",
"hasInstallScript": true,
"license": "MIT",
"funding": {
@@ -16272,9 +16439,9 @@
}
},
"node_modules/electron-log": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-5.0.1.tgz",
- "integrity": "sha512-x4wnwHg00h/onWQgjmvcdLV7Mrd9TZjxNs8LmXVpqvANDf4FsSs5wLlzOykWLcaFzR3+5hdVEQ8ctmrUxgHlPA==",
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-5.2.2.tgz",
+ "integrity": "sha512-fgvx6srjIHDowJD8WAAjoAXmiTyOz6JnGQoxOtk1mXw7o4S+HutuPHLCsk24xTXqWZgy4uO63NbedG+oEvldLw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -21127,9 +21294,9 @@
"peer": true
},
"node_modules/html-loader": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/html-loader/-/html-loader-5.0.0.tgz",
- "integrity": "sha512-puaGKdjdVVIFRtgIC2n5dt5bt0N5j6heXlAQZ4Do1MLjHmOT1gCE1Ogg7XZNeJlnOVHHsrZKGs5dfh+XwZ3XPw==",
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/html-loader/-/html-loader-5.1.0.tgz",
+ "integrity": "sha512-Jb3xwDbsm0W3qlXrCZwcYqYGnYz55hb6aoKQTlzyZPXsPpi6tHXzAfqalecglMQgNvtEfxrCQPaKT90Irt5XDA==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -30889,9 +31056,9 @@
}
},
"node_modules/postcss": {
- "version": "8.4.38",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
- "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
+ "version": "8.4.47",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
+ "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
"dev": true,
"funding": [
{
@@ -30910,8 +31077,8 @@
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.7",
- "picocolors": "^1.0.0",
- "source-map-js": "^1.2.0"
+ "picocolors": "^1.1.0",
+ "source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
@@ -38412,19 +38579,19 @@
}
},
"node_modules/webpack": {
- "version": "5.94.0",
- "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz",
- "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==",
+ "version": "5.96.1",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.96.1.tgz",
+ "integrity": "sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@types/estree": "^1.0.5",
+ "@types/eslint-scope": "^3.7.7",
+ "@types/estree": "^1.0.6",
"@webassemblyjs/ast": "^1.12.1",
"@webassemblyjs/wasm-edit": "^1.12.1",
"@webassemblyjs/wasm-parser": "^1.12.1",
- "acorn": "^8.7.1",
- "acorn-import-attributes": "^1.9.5",
- "browserslist": "^4.21.10",
+ "acorn": "^8.14.0",
+ "browserslist": "^4.24.0",
"chrome-trace-event": "^1.0.2",
"enhanced-resolve": "^5.17.1",
"es-module-lexer": "^1.2.1",
@@ -38853,6 +39020,39 @@
"ajv": "^6.9.1"
}
},
+ "node_modules/webpack/node_modules/browserslist": {
+ "version": "4.24.2",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz",
+ "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "caniuse-lite": "^1.0.30001669",
+ "electron-to-chromium": "^1.5.41",
+ "node-releases": "^2.0.18",
+ "update-browserslist-db": "^1.1.1"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
"node_modules/webpack/node_modules/eslint-scope": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
diff --git a/package.json b/package.json
index 1018a1bd262..368da367e85 100644
--- a/package.json
+++ b/package.json
@@ -95,7 +95,7 @@
"css-loader": "7.1.2",
"electron": "32.1.1",
"electron-builder": "24.13.3",
- "electron-log": "5.0.1",
+ "electron-log": "5.2.2",
"electron-reload": "2.0.0-alpha.1",
"electron-store": "8.2.0",
"electron-updater": "6.3.9",
@@ -112,7 +112,7 @@
"gulp-json-editor": "2.6.0",
"gulp-replace": "1.1.4",
"gulp-zip": "6.0.0",
- "html-loader": "5.0.0",
+ "html-loader": "5.1.0",
"html-webpack-injector": "1.1.4",
"html-webpack-plugin": "5.6.3",
"husky": "9.1.4",
@@ -123,7 +123,7 @@
"lint-staged": "15.2.8",
"mini-css-extract-plugin": "2.9.1",
"node-ipc": "9.2.1",
- "postcss": "8.4.38",
+ "postcss": "8.4.47",
"postcss-loader": "8.1.1",
"prettier": "3.3.3",
"prettier-plugin-tailwindcss": "0.6.8",
@@ -143,7 +143,7 @@
"url": "0.11.4",
"util": "0.12.5",
"wait-on": "8.0.1",
- "webpack": "5.94.0",
+ "webpack": "5.96.1",
"webpack-cli": "5.1.4",
"webpack-dev-server": "5.0.4",
"webpack-node-externals": "3.0.0"
@@ -174,7 +174,7 @@
"bufferutil": "4.0.8",
"chalk": "4.1.2",
"commander": "11.1.0",
- "core-js": "3.36.1",
+ "core-js": "3.39.0",
"form-data": "4.0.0",
"https-proxy-agent": "7.0.5",
"inquirer": "8.2.6",
|