From b750b6c082f45592a76ea7cf0ad06ff0ce6c961c Mon Sep 17 00:00:00 2001 From: Github Actions Date: Wed, 15 Jan 2025 22:10:26 +0000 Subject: [PATCH 01/13] Bumped client version(s) --- apps/desktop/package.json | 2 +- apps/desktop/src/package-lock.json | 4 ++-- apps/desktop/src/package.json | 2 +- package-lock.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 8f6c6525a39..aff1d0ffbb0 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,7 +1,7 @@ { "name": "@bitwarden/desktop", "description": "A secure and free password manager for all of your devices.", - "version": "2025.1.2", + "version": "2025.1.3", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index d878e1af2aa..e825bd41581 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/desktop", - "version": "2025.1.2", + "version": "2025.1.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2025.1.2", + "version": "2025.1.3", "license": "GPL-3.0", "dependencies": { "@bitwarden/desktop-napi": "file:../desktop_native/napi" diff --git a/apps/desktop/src/package.json b/apps/desktop/src/package.json index 08bdd745063..6feed970798 100644 --- a/apps/desktop/src/package.json +++ b/apps/desktop/src/package.json @@ -2,7 +2,7 @@ "name": "@bitwarden/desktop", "productName": "Bitwarden", "description": "A secure and free password manager for all of your devices.", - "version": "2025.1.2", + "version": "2025.1.3", "author": "Bitwarden Inc. (https://bitwarden.com)", "homepage": "https://bitwarden.com", "license": "GPL-3.0", diff --git a/package-lock.json b/package-lock.json index 9544eb398a5..ae473f3e5f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -232,7 +232,7 @@ }, "apps/desktop": { "name": "@bitwarden/desktop", - "version": "2025.1.2", + "version": "2025.1.3", "hasInstallScript": true, "license": "GPL-3.0" }, From ffa5afb5e81bc4a231c45c5ead94ee1514b6762d Mon Sep 17 00:00:00 2001 From: Todd Martin <106564991+trmartin4@users.noreply.github.com> Date: Wed, 15 Jan 2025 17:30:01 -0500 Subject: [PATCH 02/13] Renamed group for consistency with server renovate (#12896) --- .github/renovate.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/renovate.json b/.github/renovate.json index 350484b5c28..150ac1ac99d 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -4,7 +4,7 @@ "enabledManagers": ["cargo", "github-actions", "npm"], "packageRules": [ { - "groupName": "github action dependencies", + "groupName": "github-action minor", "matchManagers": ["github-actions"], "matchUpdateTypes": ["minor"] }, From e4e436b76872d2f39ee66afc8b670c3964467e91 Mon Sep 17 00:00:00 2001 From: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Date: Thu, 16 Jan 2025 13:12:29 +1000 Subject: [PATCH 03/13] [PM-15182] Remove remove-provider-export-permission feature flag (#12878) * Remove remove-provider-export feature flag * Remove ts-strict comment * Revert changes to tests --- .../layouts/organization-layout.component.ts | 5 +-- .../organization-settings-routing.module.ts | 33 +++---------------- .../navigation-switcher.stories.ts | 3 -- .../product-switcher.stories.ts | 3 -- .../shared/product-switcher.service.spec.ts | 3 -- .../organization.service.abstraction.ts | 2 +- .../vnext.organization.service.ts | 2 +- .../models/domain/organization.ts | 6 +--- libs/common/src/enums/feature-flag.enum.ts | 2 -- 9 files changed, 9 insertions(+), 50 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts index 0b024817edc..c1112c51e39 100644 --- a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts +++ b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.ts @@ -77,10 +77,7 @@ export class OrganizationLayoutComponent implements OnInit { filter((org) => org != null), ); - this.canAccessExport$ = combineLatest([ - this.organization$, - this.configService.getFeatureFlag$(FeatureFlag.PM11360RemoveProviderExportPermission), - ]).pipe(map(([org, removeProviderExport]) => org.canAccessExport(removeProviderExport))); + this.canAccessExport$ = this.organization$.pipe(map((org) => org.canAccessExport)); this.showPaymentAndHistory$ = this.organization$.pipe( map( diff --git a/apps/web/src/app/admin-console/organizations/settings/organization-settings-routing.module.ts b/apps/web/src/app/admin-console/organizations/settings/organization-settings-routing.module.ts index ac2c7448b0a..06ceaa0d9c7 100644 --- a/apps/web/src/app/admin-console/organizations/settings/organization-settings-routing.module.ts +++ b/apps/web/src/app/admin-console/organizations/settings/organization-settings-routing.module.ts @@ -1,13 +1,8 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { inject, NgModule } from "@angular/core"; -import { CanMatchFn, RouterModule, Routes } from "@angular/router"; -import { map } from "rxjs"; +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; import { canAccessSettingsTab } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { Organization } from "@bitwarden/common/admin-console/models/domain/organization"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { organizationPermissionsGuard } from "../../organizations/guards/org-permissions.guard"; import { organizationRedirectGuard } from "../../organizations/guards/org-redirect.guard"; @@ -16,11 +11,6 @@ import { PoliciesComponent } from "../../organizations/policies"; import { AccountComponent } from "./account.component"; import { TwoFactorSetupComponent } from "./two-factor-setup.component"; -const removeProviderExportPermission$: CanMatchFn = () => - inject(ConfigService) - .getFeatureFlag$(FeatureFlag.PM11360RemoveProviderExportPermission) - .pipe(map((removeProviderExport) => removeProviderExport === true)); - const routes: Routes = [ { path: "", @@ -68,27 +58,13 @@ const routes: Routes = [ titleId: "importData", }, }, - - // Export routing is temporarily duplicated to set the flag value passed into org.canAccessExport { path: "export", loadComponent: () => import("../tools/vault-export/org-vault-export.component").then( (mod) => mod.OrganizationVaultExportComponent, ), - canMatch: [removeProviderExportPermission$], // if this matches, the flag is ON - canActivate: [organizationPermissionsGuard((org) => org.canAccessExport(true))], - data: { - titleId: "exportVault", - }, - }, - { - path: "export", - loadComponent: () => - import("../tools/vault-export/org-vault-export.component").then( - (mod) => mod.OrganizationVaultExportComponent, - ), - canActivate: [organizationPermissionsGuard((org) => org.canAccessExport(false))], + canActivate: [organizationPermissionsGuard((org) => org.canAccessExport)], data: { titleId: "exportVault", }, @@ -118,7 +94,8 @@ function getSettingsRoute(organization: Organization) { if (organization.canManageDeviceApprovals) { return "device-approvals"; } - return undefined; + + return "/"; } @NgModule({ diff --git a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts index a7ff50b4264..1c15f7cc7c1 100644 --- a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts +++ b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.stories.ts @@ -157,7 +157,6 @@ export const SMAvailable: Story = { canManageUsers: false, canAccessSecretsManager: true, enabled: true, - canAccessExport: (_) => false, }, ] as Organization[], mockProviders: [], @@ -173,7 +172,6 @@ export const SMAndACAvailable: Story = { canManageUsers: true, canAccessSecretsManager: true, enabled: true, - canAccessExport: (_) => false, }, ] as Organization[], mockProviders: [], @@ -189,7 +187,6 @@ export const WithAllOptions: Story = { canManageUsers: true, canAccessSecretsManager: true, enabled: true, - canAccessExport: (_) => false, }, ] as Organization[], mockProviders: [{ id: "provider-a" }] as Provider[], diff --git a/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts b/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts index b53d0243f64..7a4df4bad00 100644 --- a/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts +++ b/apps/web/src/app/layouts/product-switcher/product-switcher.stories.ts @@ -176,7 +176,6 @@ export const WithSM: Story = { canManageUsers: false, canAccessSecretsManager: true, enabled: true, - canAccessExport: (_) => false, }, ] as Organization[], mockProviders: [], @@ -192,7 +191,6 @@ export const WithSMAndAC: Story = { canManageUsers: true, canAccessSecretsManager: true, enabled: true, - canAccessExport: (_) => false, }, ] as Organization[], mockProviders: [], @@ -208,7 +206,6 @@ export const WithAllOptions: Story = { canManageUsers: true, canAccessSecretsManager: true, enabled: true, - canAccessExport: (_) => false, }, ] as Organization[], mockProviders: [{ id: "provider-a" }] as Provider[], diff --git a/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.spec.ts b/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.spec.ts index a071d0f8852..919b3be0424 100644 --- a/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.spec.ts +++ b/apps/web/src/app/layouts/product-switcher/shared/product-switcher.service.spec.ts @@ -116,7 +116,6 @@ describe("ProductSwitcherService", () => { id: "1234", canAccessSecretsManager: true, enabled: true, - canAccessExport: (_) => true, }, ] as Organization[]); @@ -232,14 +231,12 @@ describe("ProductSwitcherService", () => { canAccessSecretsManager: true, enabled: true, name: "Org 2", - canAccessExport: (_) => true, }, { id: "4243", canAccessSecretsManager: true, enabled: true, name: "Org 32", - canAccessExport: (_) => true, }, ] as Organization[]); diff --git a/libs/common/src/admin-console/abstractions/organization/organization.service.abstraction.ts b/libs/common/src/admin-console/abstractions/organization/organization.service.abstraction.ts index 2161feb516e..da81f340fda 100644 --- a/libs/common/src/admin-console/abstractions/organization/organization.service.abstraction.ts +++ b/libs/common/src/admin-console/abstractions/organization/organization.service.abstraction.ts @@ -17,7 +17,7 @@ export function canAccessSettingsTab(org: Organization): boolean { org.canManageSso || org.canManageScim || org.canAccessImport || - org.canAccessExport(false) || // Feature flag value doesn't matter here, providers will have access to this group anyway + org.canAccessExport || org.canManageDeviceApprovals ); } diff --git a/libs/common/src/admin-console/abstractions/organization/vnext.organization.service.ts b/libs/common/src/admin-console/abstractions/organization/vnext.organization.service.ts index b5c0f6291fc..c25a153a068 100644 --- a/libs/common/src/admin-console/abstractions/organization/vnext.organization.service.ts +++ b/libs/common/src/admin-console/abstractions/organization/vnext.organization.service.ts @@ -17,7 +17,7 @@ export function canAccessSettingsTab(org: Organization): boolean { org.canManageSso || org.canManageScim || org.canAccessImport || - org.canAccessExport(false) || // Feature flag value doesn't matter here, providers will have access to this group anyway + org.canAccessExport || org.canManageDeviceApprovals ); } diff --git a/libs/common/src/admin-console/models/domain/organization.ts b/libs/common/src/admin-console/models/domain/organization.ts index 8441298bbff..9dcc9f0752c 100644 --- a/libs/common/src/admin-console/models/domain/organization.ts +++ b/libs/common/src/admin-console/models/domain/organization.ts @@ -182,11 +182,7 @@ export class Organization { ); } - canAccessExport(removeProviderExport: boolean) { - if (!removeProviderExport && this.isProviderUser) { - return true; - } - + get canAccessExport() { return ( this.isMember && (this.type === OrganizationUserType.Owner || diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index dde31acb9e3..d008a09d66c 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -44,7 +44,6 @@ export enum FeatureFlag { NewDeviceVerificationPermanentDismiss = "new-device-permanent-dismiss", DisableFreeFamiliesSponsorship = "PM-12274-disable-free-families-sponsorship", MacOsNativeCredentialSync = "macos-native-credential-sync", - PM11360RemoveProviderExportPermission = "pm-11360-remove-provider-export-permission", PM12443RemovePagingLogic = "pm-12443-remove-paging-logic", PrivateKeyRegeneration = "pm-12241-private-key-regeneration", ResellerManagedOrgAlert = "PM-15814-alert-owners-of-reseller-managed-orgs", @@ -102,7 +101,6 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.NewDeviceVerificationPermanentDismiss]: FALSE, [FeatureFlag.DisableFreeFamiliesSponsorship]: FALSE, [FeatureFlag.MacOsNativeCredentialSync]: FALSE, - [FeatureFlag.PM11360RemoveProviderExportPermission]: FALSE, [FeatureFlag.PM12443RemovePagingLogic]: FALSE, [FeatureFlag.PrivateKeyRegeneration]: FALSE, [FeatureFlag.ResellerManagedOrgAlert]: FALSE, From ca420d73143987c447e79fc9d091b744cc9b25fd Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 16 Jan 2025 11:02:16 +0100 Subject: [PATCH 04/13] Attempt to fix snap build (#12882) * Attempt to fix snap build * Move snap * Add debug logging * Fix move * Remove debug logs --- apps/desktop/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index aff1d0ffbb0..eaa27c3eb9f 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -35,7 +35,7 @@ "clean:dist": "rimraf ./dist", "pack:dir": "npm run clean:dist && electron-builder --dir -p never", "pack:lin:flatpak": "npm run clean:dist && electron-builder --dir -p never && flatpak-builder --repo=build/.repo build/.flatpak ./resources/com.bitwarden.desktop.devel.yaml --install-deps-from=flathub --force-clean && flatpak build-bundle ./build/.repo/ ./dist/com.bitwarden.desktop.flatpak com.bitwarden.desktop", - "pack:lin": "npm run clean:dist && electron-builder --linux --x64 -p never && export SNAP_FILE=$(realpath ./dist/bitwarden_*.snap) && unsquashfs -d ./dist/tmp-snap/ $SNAP_FILE && mkdir -p ./dist/tmp-snap/meta/polkit/ && cp ./resources/com.bitwarden.desktop.policy ./dist/tmp-snap/meta/polkit/polkit.com.bitwarden.desktop.policy && rm $SNAP_FILE && mksquashfs ./dist/tmp-snap/ $SNAP_FILE -noappend -comp lzo -no-fragments && rm -rf ./dist/tmp-snap/", + "pack:lin": "npm run clean:dist && electron-builder --linux --x64 -p never && export SNAP_FILE=$(realpath ./dist/bitwarden_*.snap) && unsquashfs -d ./dist/tmp-snap/ $SNAP_FILE && mkdir -p ./dist/tmp-snap/meta/polkit/ && cp ./resources/com.bitwarden.desktop.policy ./dist/tmp-snap/meta/polkit/polkit.com.bitwarden.desktop.policy && rm $SNAP_FILE && snapcraft pack ./dist/tmp-snap/ && mv ./*.snap ./dist/ && rm -rf ./dist/tmp-snap/", "pack:mac": "npm run clean:dist && electron-builder --mac --universal -p never", "pack:mac:arm64": "npm run clean:dist && electron-builder --mac --arm64 -p never", "pack:mac:mas": "npm run clean:dist && electron-builder --mac mas --universal -p never", From 68e02bc236e33c3e558f197439c2bb1f22940597 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 16 Jan 2025 15:45:01 +0100 Subject: [PATCH 05/13] [deps] SM: Update eslint-plugin-tailwindcss to v3.17.5 (#11535) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Oscar Hinton --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index ae473f3e5f9..ac31cc82586 100644 --- a/package-lock.json +++ b/package-lock.json @@ -147,7 +147,7 @@ "eslint-plugin-rxjs": "5.0.3", "eslint-plugin-rxjs-angular": "2.0.1", "eslint-plugin-storybook": "0.8.0", - "eslint-plugin-tailwindcss": "3.17.4", + "eslint-plugin-tailwindcss": "3.17.5", "html-loader": "5.1.0", "html-webpack-injector": "1.1.4", "html-webpack-plugin": "5.6.3", @@ -16540,9 +16540,9 @@ } }, "node_modules/eslint-plugin-tailwindcss": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-tailwindcss/-/eslint-plugin-tailwindcss-3.17.4.tgz", - "integrity": "sha512-gJAEHmCq2XFfUP/+vwEfEJ9igrPeZFg+skeMtsxquSQdxba9XRk5bn0Bp9jxG1VV9/wwPKi1g3ZjItu6MIjhNg==", + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-tailwindcss/-/eslint-plugin-tailwindcss-3.17.5.tgz", + "integrity": "sha512-8Mi7p7dm+mO1dHgRHHFdPu4RDTBk69Cn4P0B40vRQR+MrguUpwmKwhZy1kqYe3Km8/4nb+cyrCF+5SodOEmaow==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 03d1f3d3c75..cd4bc770790 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,7 @@ "eslint-plugin-rxjs": "5.0.3", "eslint-plugin-rxjs-angular": "2.0.1", "eslint-plugin-storybook": "0.8.0", - "eslint-plugin-tailwindcss": "3.17.4", + "eslint-plugin-tailwindcss": "3.17.5", "html-loader": "5.1.0", "html-webpack-injector": "1.1.4", "html-webpack-plugin": "5.6.3", From 5ba5f04e72c20be4384a508f15264d0bb1cb7e49 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 16 Jan 2025 15:45:10 +0100 Subject: [PATCH 06/13] [deps] SM: Update husky to v9.1.7 (#10846) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Oscar Hinton --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index ac31cc82586..11625f0fd9b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -151,7 +151,7 @@ "html-loader": "5.1.0", "html-webpack-injector": "1.1.4", "html-webpack-plugin": "5.6.3", - "husky": "9.1.4", + "husky": "9.1.7", "jest-diff": "29.7.0", "jest-junit": "16.0.0", "jest-mock-extended": "3.0.7", @@ -18972,9 +18972,9 @@ } }, "node_modules/husky": { - "version": "9.1.4", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.4.tgz", - "integrity": "sha512-bho94YyReb4JV7LYWRWxZ/xr6TtOTt8cMfmQ39MQYJ7f/YE268s3GdghGwi+y4zAeqewE5zYLvuhV0M0ijsDEA==", + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", "dev": true, "license": "MIT", "bin": { diff --git a/package.json b/package.json index cd4bc770790..02ffd22a198 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,7 @@ "html-loader": "5.1.0", "html-webpack-injector": "1.1.4", "html-webpack-plugin": "5.6.3", - "husky": "9.1.4", + "husky": "9.1.7", "jest-diff": "29.7.0", "jest-junit": "16.0.0", "jest-mock-extended": "3.0.7", From 51717cab07bfddf4be2eb6e80d28650a3fda865a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 16 Jan 2025 15:45:20 +0100 Subject: [PATCH 07/13] [deps] SM: Update eslint to v8.57.1 (#11317) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Oscar Hinton --- package-lock.json | 26 +++++++++++++------------- package.json | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 11625f0fd9b..b94ad3b762f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -140,7 +140,7 @@ "electron-reload": "2.0.0-alpha.1", "electron-store": "8.2.0", "electron-updater": "6.3.9", - "eslint": "8.57.0", + "eslint": "8.57.1", "eslint-config-prettier": "9.1.0", "eslint-import-resolver-typescript": "3.6.1", "eslint-plugin-import": "2.29.1", @@ -5905,9 +5905,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, "license": "MIT", "engines": { @@ -6013,14 +6013,14 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", "deprecated": "Use @eslint/config-array instead", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", + "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" }, @@ -16091,9 +16091,9 @@ } }, "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", @@ -16101,8 +16101,8 @@ "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", diff --git a/package.json b/package.json index 02ffd22a198..07e3f217867 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,7 @@ "electron-reload": "2.0.0-alpha.1", "electron-store": "8.2.0", "electron-updater": "6.3.9", - "eslint": "8.57.0", + "eslint": "8.57.1", "eslint-config-prettier": "9.1.0", "eslint-import-resolver-typescript": "3.6.1", "eslint-plugin-import": "2.29.1", From ad8694b641c31640637b139d288c28145ad8b368 Mon Sep 17 00:00:00 2001 From: Vijay Oommen Date: Thu, 16 Jan 2025 08:47:36 -0600 Subject: [PATCH 08/13] PM-15070 Star critical apps (#12109) Ability to star a record when flagged as critical. This is still behind a feature flag --- .../risk-insights/models/password-health.ts | 4 + .../critical-apps-api.service.spec.ts | 79 +++++++++ .../services/critical-apps-api.service.ts | 39 +++++ .../services/critical-apps.service.spec.ts | 142 ++++++++++++++++ .../services/critical-apps.service.ts | 159 ++++++++++++++++++ .../reports/risk-insights/services/index.ts | 2 + .../access-intelligence.module.ts | 15 ++ .../all-applications.component.html | 8 +- .../all-applications.component.ts | 104 +++++++----- .../application-table.mock.ts | 6 + .../risk-insights.component.html | 2 +- .../risk-insights.component.ts | 13 +- 12 files changed, 524 insertions(+), 49 deletions(-) create mode 100644 bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps-api.service.spec.ts create mode 100644 bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps-api.service.ts create mode 100644 bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps.service.spec.ts create mode 100644 bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps.service.ts diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts index 94dad65fdc9..947fc8a79d3 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/models/password-health.ts @@ -30,6 +30,10 @@ export type ApplicationHealthReportDetail = { atRiskMemberDetails: MemberDetailsFlat[]; }; +export type ApplicationHealthReportDetailWithCriticalFlag = ApplicationHealthReportDetail & { + isMarkedAsCritical: boolean; +}; + /** * Breaks the cipher health info out by uri and passes * along the password health and member info diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps-api.service.spec.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps-api.service.spec.ts new file mode 100644 index 00000000000..838dc2c8241 --- /dev/null +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps-api.service.spec.ts @@ -0,0 +1,79 @@ +import { mock } from "jest-mock-extended"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationId } from "@bitwarden/common/types/guid"; + +import { CriticalAppsApiService } from "./critical-apps-api.service"; +import { + PasswordHealthReportApplicationId, + PasswordHealthReportApplicationsRequest, + PasswordHealthReportApplicationsResponse, +} from "./critical-apps.service"; + +describe("CriticalAppsApiService", () => { + let service: CriticalAppsApiService; + const apiService = mock(); + + beforeEach(() => { + service = new CriticalAppsApiService(apiService); + }); + + it("should be created", () => { + expect(service).toBeTruthy(); + }); + + it("should call apiService.send with correct parameters for SaveCriticalApps", (done) => { + const requests: PasswordHealthReportApplicationsRequest[] = [ + { organizationId: "org1" as OrganizationId, url: "test one" }, + { organizationId: "org1" as OrganizationId, url: "test two" }, + ]; + const response: PasswordHealthReportApplicationsResponse[] = [ + { + id: "1" as PasswordHealthReportApplicationId, + organizationId: "org1" as OrganizationId, + uri: "test one", + }, + { + id: "2" as PasswordHealthReportApplicationId, + organizationId: "org1" as OrganizationId, + uri: "test two", + }, + ]; + + apiService.send.mockReturnValue(Promise.resolve(response)); + + service.saveCriticalApps(requests).subscribe((result) => { + expect(result).toEqual(response); + expect(apiService.send).toHaveBeenCalledWith( + "POST", + "/reports/password-health-report-applications/", + requests, + true, + true, + ); + done(); + }); + }); + + it("should call apiService.send with correct parameters for GetCriticalApps", (done) => { + const orgId: OrganizationId = "org1" as OrganizationId; + const response: PasswordHealthReportApplicationsResponse[] = [ + { id: "1" as PasswordHealthReportApplicationId, organizationId: orgId, uri: "test one" }, + { id: "2" as PasswordHealthReportApplicationId, organizationId: orgId, uri: "test two" }, + ]; + + apiService.send.mockReturnValue(Promise.resolve(response)); + + service.getCriticalApps(orgId).subscribe((result) => { + expect(result).toEqual(response); + expect(apiService.send).toHaveBeenCalledWith( + "GET", + `/reports/password-health-report-applications/${orgId.toString()}`, + null, + true, + true, + ); + done(); + }); + }); +}); diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps-api.service.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps-api.service.ts new file mode 100644 index 00000000000..edd2cf34b56 --- /dev/null +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps-api.service.ts @@ -0,0 +1,39 @@ +import { from, Observable } from "rxjs"; + +import { ApiService } from "@bitwarden/common/abstractions/api.service"; +import { OrganizationId } from "@bitwarden/common/types/guid"; + +import { + PasswordHealthReportApplicationsRequest, + PasswordHealthReportApplicationsResponse, +} from "./critical-apps.service"; + +export class CriticalAppsApiService { + constructor(private apiService: ApiService) {} + + saveCriticalApps( + requests: PasswordHealthReportApplicationsRequest[], + ): Observable { + const dbResponse = this.apiService.send( + "POST", + "/reports/password-health-report-applications/", + requests, + true, + true, + ); + + return from(dbResponse as Promise); + } + + getCriticalApps(orgId: OrganizationId): Observable { + const dbResponse = this.apiService.send( + "GET", + `/reports/password-health-report-applications/${orgId.toString()}`, + null, + true, + true, + ); + + return from(dbResponse as Promise); + } +} diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps.service.spec.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps.service.spec.ts new file mode 100644 index 00000000000..c6c4562310e --- /dev/null +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps.service.spec.ts @@ -0,0 +1,142 @@ +import { randomUUID } from "crypto"; + +import { fakeAsync, flush } from "@angular/core/testing"; +import { mock } from "jest-mock-extended"; +import { of } from "rxjs"; + +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; +import { CsprngArray } from "@bitwarden/common/types/csprng"; +import { OrganizationId } from "@bitwarden/common/types/guid"; +import { OrgKey } from "@bitwarden/common/types/key"; +import { KeyService } from "@bitwarden/key-management"; + +import { CriticalAppsApiService } from "./critical-apps-api.service"; +import { + CriticalAppsService, + PasswordHealthReportApplicationId, + PasswordHealthReportApplicationsRequest, + PasswordHealthReportApplicationsResponse, +} from "./critical-apps.service"; + +describe("CriticalAppsService", () => { + let service: CriticalAppsService; + const keyService = mock(); + const encryptService = mock(); + const criticalAppsApiService = mock({ + saveCriticalApps: jest.fn(), + getCriticalApps: jest.fn(), + }); + + beforeEach(() => { + service = new CriticalAppsService(keyService, encryptService, criticalAppsApiService); + + // reset mocks + jest.resetAllMocks(); + }); + + it("should be created", () => { + expect(service).toBeTruthy(); + }); + + it("should set critical apps", async () => { + // arrange + const criticalApps = ["https://example.com", "https://example.org"]; + + const request = [ + { organizationId: "org1", url: "encryptedUrlName" }, + { organizationId: "org1", url: "encryptedUrlName" }, + ] as PasswordHealthReportApplicationsRequest[]; + + const response = [ + { id: "id1", organizationId: "org1", uri: "https://example.com" }, + { id: "id2", organizationId: "org1", uri: "https://example.org" }, + ] as PasswordHealthReportApplicationsResponse[]; + + encryptService.encrypt.mockResolvedValue(new EncString("encryptedUrlName")); + criticalAppsApiService.saveCriticalApps.mockReturnValue(of(response)); + + // act + await service.setCriticalApps("org1", criticalApps); + + // expectations + expect(keyService.getOrgKey).toHaveBeenCalledWith("org1"); + expect(encryptService.encrypt).toHaveBeenCalledTimes(2); + expect(criticalAppsApiService.saveCriticalApps).toHaveBeenCalledWith(request); + }); + + it("should exclude records that already exist", async () => { + // arrange + // one record already exists + service.setAppsInListForOrg([ + { + id: randomUUID() as PasswordHealthReportApplicationId, + organizationId: "org1" as OrganizationId, + uri: "https://example.com", + }, + ]); + + // two records are selected - one already in the database + const selectedUrls = ["https://example.com", "https://example.org"]; + + // expect only one record to be sent to the server + const request = [ + { organizationId: "org1", url: "encryptedUrlName" }, + ] as PasswordHealthReportApplicationsRequest[]; + + // mocked response + const response = [ + { id: "id1", organizationId: "org1", uri: "test" }, + ] as PasswordHealthReportApplicationsResponse[]; + + encryptService.encrypt.mockResolvedValue(new EncString("encryptedUrlName")); + criticalAppsApiService.saveCriticalApps.mockReturnValue(of(response)); + + // act + await service.setCriticalApps("org1", selectedUrls); + + // expectations + expect(keyService.getOrgKey).toHaveBeenCalledWith("org1"); + expect(encryptService.encrypt).toHaveBeenCalledTimes(1); + expect(criticalAppsApiService.saveCriticalApps).toHaveBeenCalledWith(request); + }); + + it("should get critical apps", fakeAsync(() => { + const orgId = "org1" as OrganizationId; + const response = [ + { id: "id1", organizationId: "org1", uri: "https://example.com" }, + { id: "id2", organizationId: "org1", uri: "https://example.org" }, + ] as PasswordHealthReportApplicationsResponse[]; + + encryptService.decryptToUtf8.mockResolvedValue("https://example.com"); + criticalAppsApiService.getCriticalApps.mockReturnValue(of(response)); + + const mockRandomBytes = new Uint8Array(64) as CsprngArray; + const mockOrgKey = new SymmetricCryptoKey(mockRandomBytes) as OrgKey; + keyService.getOrgKey.mockResolvedValue(mockOrgKey); + + service.setOrganizationId(orgId as OrganizationId); + flush(); + + expect(keyService.getOrgKey).toHaveBeenCalledWith(orgId.toString()); + expect(encryptService.decryptToUtf8).toHaveBeenCalledTimes(2); + expect(criticalAppsApiService.getCriticalApps).toHaveBeenCalledWith(orgId); + })); + + it("should get by org id", () => { + const orgId = "org1" as OrganizationId; + const response = [ + { id: "id1", organizationId: "org1", uri: "https://example.com" }, + { id: "id2", organizationId: "org1", uri: "https://example.org" }, + { id: "id3", organizationId: "org2", uri: "https://example.org" }, + { id: "id4", organizationId: "org2", uri: "https://example.org" }, + ] as PasswordHealthReportApplicationsResponse[]; + + service.setAppsInListForOrg(response); + + service.getAppsListForOrg(orgId as OrganizationId).subscribe((res) => { + expect(res).toHaveLength(2); + }); + }); +}); diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps.service.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps.service.ts new file mode 100644 index 00000000000..10b7d3f1fbb --- /dev/null +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/critical-apps.service.ts @@ -0,0 +1,159 @@ +import { + BehaviorSubject, + first, + firstValueFrom, + forkJoin, + from, + map, + Observable, + of, + Subject, + switchMap, + takeUntil, + zip, +} from "rxjs"; +import { Opaque } from "type-fest"; + +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; +import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; +import { OrganizationId } from "@bitwarden/common/types/guid"; +import { OrgKey } from "@bitwarden/common/types/key"; +import { KeyService } from "@bitwarden/key-management"; + +import { CriticalAppsApiService } from "./critical-apps-api.service"; + +/* Retrieves and decrypts critical apps for a given organization + * Encrypts and saves data for a given organization + */ +export class CriticalAppsService { + private orgId = new BehaviorSubject(null); + private criticalAppsList = new BehaviorSubject([]); + private teardown = new Subject(); + + private fetchOrg$ = this.orgId + .pipe( + switchMap((orgId) => this.retrieveCriticalApps(orgId)), + takeUntil(this.teardown), + ) + .subscribe((apps) => this.criticalAppsList.next(apps)); + + constructor( + private keyService: KeyService, + private encryptService: EncryptService, + private criticalAppsApiService: CriticalAppsApiService, + ) {} + + // Get a list of critical apps for a given organization + getAppsListForOrg(orgId: string): Observable { + return this.criticalAppsList + .asObservable() + .pipe(map((apps) => apps.filter((app) => app.organizationId === orgId))); + } + + // Reset the critical apps list + setAppsInListForOrg(apps: PasswordHealthReportApplicationsResponse[]) { + this.criticalAppsList.next(apps); + } + + // Save the selected critical apps for a given organization + async setCriticalApps(orgId: string, selectedUrls: string[]) { + const key = await this.keyService.getOrgKey(orgId); + + // only save records that are not already in the database + const newEntries = await this.filterNewEntries(orgId as OrganizationId, selectedUrls); + const criticalAppsRequests = await this.encryptNewEntries( + orgId as OrganizationId, + key, + newEntries, + ); + + const dbResponse = await firstValueFrom( + this.criticalAppsApiService.saveCriticalApps(criticalAppsRequests), + ); + + // add the new entries to the criticalAppsList + const updatedList = [...this.criticalAppsList.value]; + for (const responseItem of dbResponse) { + const decryptedUrl = await this.encryptService.decryptToUtf8( + new EncString(responseItem.uri), + key, + ); + if (!updatedList.some((f) => f.uri === decryptedUrl)) { + updatedList.push({ + id: responseItem.id, + organizationId: responseItem.organizationId, + uri: decryptedUrl, + } as PasswordHealthReportApplicationsResponse); + } + } + this.criticalAppsList.next(updatedList); + } + + // Get the critical apps for a given organization + setOrganizationId(orgId: OrganizationId) { + this.orgId.next(orgId); + } + + private retrieveCriticalApps( + orgId: OrganizationId | null, + ): Observable { + if (orgId === null) { + return of([]); + } + + const result$ = zip( + this.criticalAppsApiService.getCriticalApps(orgId), + from(this.keyService.getOrgKey(orgId)), + ).pipe( + switchMap(([response, key]) => { + const results = response.map(async (r: PasswordHealthReportApplicationsResponse) => { + const encrypted = new EncString(r.uri); + const uri = await this.encryptService.decryptToUtf8(encrypted, key); + return { id: r.id, organizationId: r.organizationId, uri: uri }; + }); + return forkJoin(results); + }), + first(), + ); + + return result$ as Observable; + } + + private async filterNewEntries(orgId: OrganizationId, selectedUrls: string[]): Promise { + return await firstValueFrom(this.criticalAppsList).then((criticalApps) => { + const criticalAppsUri = criticalApps + .filter((f) => f.organizationId === orgId) + .map((f) => f.uri); + return selectedUrls.filter((url) => !criticalAppsUri.includes(url)); + }); + } + + private async encryptNewEntries( + orgId: OrganizationId, + key: OrgKey, + newEntries: string[], + ): Promise { + const criticalAppsPromises = newEntries.map(async (url) => { + const encryptedUrlName = await this.encryptService.encrypt(url, key); + return { + organizationId: orgId, + url: encryptedUrlName?.encryptedString?.toString() ?? "", + } as PasswordHealthReportApplicationsRequest; + }); + + return await Promise.all(criticalAppsPromises); + } +} + +export interface PasswordHealthReportApplicationsRequest { + organizationId: OrganizationId; + url: string; +} + +export interface PasswordHealthReportApplicationsResponse { + id: PasswordHealthReportApplicationId; + organizationId: OrganizationId; + uri: string; +} + +export type PasswordHealthReportApplicationId = Opaque; diff --git a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/index.ts b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/index.ts index a8e62437b9d..f547df31f41 100644 --- a/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/index.ts +++ b/bitwarden_license/bit-common/src/tools/reports/risk-insights/services/index.ts @@ -1,4 +1,6 @@ export * from "./member-cipher-details-api.service"; export * from "./password-health.service"; +export * from "./critical-apps.service"; +export * from "./critical-apps-api.service"; export * from "./risk-insights-report.service"; export * from "./risk-insights-data.service"; diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/access-intelligence.module.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/access-intelligence.module.ts index 2db7af4bb46..5f461ff6c49 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/access-intelligence.module.ts +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/access-intelligence.module.ts @@ -1,14 +1,19 @@ import { NgModule } from "@angular/core"; +import { safeProvider } from "@bitwarden/angular/platform/utils/safe-provider"; +import { CriticalAppsService } from "@bitwarden/bit-common/tools/reports/risk-insights"; import { + CriticalAppsApiService, MemberCipherDetailsApiService, RiskInsightsDataService, RiskInsightsReportService, } from "@bitwarden/bit-common/tools/reports/risk-insights/services"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { AuditService } from "@bitwarden/common/abstractions/audit.service"; +import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength/password-strength.service.abstraction"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { KeyService } from "@bitwarden/key-management"; import { AccessIntelligenceRoutingModule } from "./access-intelligence-routing.module"; import { RiskInsightsComponent } from "./risk-insights.component"; @@ -33,6 +38,16 @@ import { RiskInsightsComponent } from "./risk-insights.component"; provide: RiskInsightsDataService, deps: [RiskInsightsReportService], }, + safeProvider({ + provide: CriticalAppsService, + useClass: CriticalAppsService, + deps: [KeyService, EncryptService, CriticalAppsApiService], + }), + safeProvider({ + provide: CriticalAppsApiService, + useClass: CriticalAppsApiService, + deps: [ApiService], + }), ], }) export class AccessIntelligenceModule {} diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.html index e17ac078687..bcc15fbc8fc 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.html +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.html @@ -55,7 +55,7 @@

{{ "allApplications" | i18n }}

buttonType="secondary" bitButton *ngIf="isCriticalAppsFeatureEnabled" - [disabled]="!selectedIds.size" + [disabled]="!selectedUrls.size" [loading]="markingAsCritical" (click)="markAppsAsCritical()" > @@ -80,9 +80,11 @@

{{ "allApplications" | i18n }}

+ {{ r.applicationName }} diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts index 5fb12fed090..b22b94599f9 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/all-applications.component.ts @@ -1,15 +1,17 @@ -import { Component, DestroyRef, OnDestroy, OnInit, inject } from "@angular/core"; +import { Component, DestroyRef, inject, OnInit } from "@angular/core"; import { takeUntilDestroyed } from "@angular/core/rxjs-interop"; import { FormControl } from "@angular/forms"; import { ActivatedRoute } from "@angular/router"; -import { debounceTime, map, Observable, of, Subscription } from "rxjs"; +import { combineLatest, debounceTime, map, Observable, of, skipWhile } from "rxjs"; import { + CriticalAppsService, RiskInsightsDataService, RiskInsightsReportService, } from "@bitwarden/bit-common/tools/reports/risk-insights"; import { ApplicationHealthReportDetail, + ApplicationHealthReportDetailWithCriticalFlag, ApplicationHealthReportSummary, } from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health"; import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; @@ -50,16 +52,15 @@ import { ApplicationsLoadingComponent } from "./risk-insights-loading.component" SharedModule, ], }) -export class AllApplicationsComponent implements OnInit, OnDestroy { - protected dataSource = new TableDataSource(); - protected selectedIds: Set = new Set(); +export class AllApplicationsComponent implements OnInit { + protected dataSource = new TableDataSource(); + protected selectedUrls: Set = new Set(); protected searchControl = new FormControl("", { nonNullable: true }); protected loading = true; protected organization = {} as Organization; noItemsIcon = Icons.Security; protected markingAsCritical = false; protected applicationSummary = {} as ApplicationHealthReportSummary; - private subscription = new Subscription(); destroyRef = inject(DestroyRef); isLoading$: Observable = of(false); @@ -70,28 +71,33 @@ export class AllApplicationsComponent implements OnInit, OnDestroy { FeatureFlag.CriticalApps, ); - const organizationId = this.activatedRoute.snapshot.paramMap.get("organizationId"); - - if (organizationId) { - this.organization = await this.organizationService.get(organizationId); - this.subscription = this.dataService.applications$ - .pipe( - map((applications) => { - if (applications) { - this.dataSource.data = applications; - this.applicationSummary = - this.reportService.generateApplicationsSummary(applications); - } - }), - takeUntilDestroyed(this.destroyRef), - ) - .subscribe(); - this.isLoading$ = this.dataService.isLoading$; - } - } - - ngOnDestroy(): void { - this.subscription?.unsubscribe(); + const organizationId = this.activatedRoute.snapshot.paramMap.get("organizationId") ?? ""; + combineLatest([ + this.dataService.applications$, + this.criticalAppsService.getAppsListForOrg(organizationId), + this.organizationService.get$(organizationId), + ]) + .pipe( + takeUntilDestroyed(this.destroyRef), + skipWhile(([_, __, organization]) => !organization), + map(([applications, criticalApps, organization]) => { + const criticalUrls = criticalApps.map((ca) => ca.uri); + const data = applications?.map((app) => ({ + ...app, + isMarkedAsCritical: criticalUrls.includes(app.applicationName), + })) as ApplicationHealthReportDetailWithCriticalFlag[]; + return { data, organization }; + }), + ) + .subscribe(({ data, organization }) => { + this.dataSource.data = data ?? []; + this.applicationSummary = this.reportService.generateApplicationsSummary(data ?? []); + if (organization) { + this.organization = organization; + } + }); + + this.isLoading$ = this.dataService.isLoading$; } constructor( @@ -103,6 +109,7 @@ export class AllApplicationsComponent implements OnInit, OnDestroy { protected dataService: RiskInsightsDataService, protected organizationService: OrganizationService, protected reportService: RiskInsightsReportService, + protected criticalAppsService: CriticalAppsService, protected dialogService: DialogService, ) { this.searchControl.valueChanges @@ -119,21 +126,28 @@ export class AllApplicationsComponent implements OnInit, OnDestroy { }); }; + isMarkedAsCriticalItem(applicationName: string) { + return this.selectedUrls.has(applicationName); + } + markAppsAsCritical = async () => { - // TODO: Send to API once implemented this.markingAsCritical = true; - return new Promise((resolve) => { - setTimeout(() => { - this.selectedIds.clear(); - this.toastService.showToast({ - variant: "success", - title: "", - message: this.i18nService.t("appsMarkedAsCritical"), - }); - resolve(true); - this.markingAsCritical = false; - }, 1000); - }); + + try { + await this.criticalAppsService.setCriticalApps( + this.organization.id, + Array.from(this.selectedUrls), + ); + + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("appsMarkedAsCritical"), + }); + } finally { + this.selectedUrls.clear(); + this.markingAsCritical = false; + } }; trackByFunction(_: number, item: ApplicationHealthReportDetail) { @@ -161,12 +175,14 @@ export class AllApplicationsComponent implements OnInit, OnDestroy { }); }; - onCheckboxChange(id: number, event: Event) { + onCheckboxChange(applicationName: string, event: Event) { const isChecked = (event.target as HTMLInputElement).checked; if (isChecked) { - this.selectedIds.add(id); + this.selectedUrls.add(applicationName); } else { - this.selectedIds.delete(id); + this.selectedUrls.delete(applicationName); } } + + getSelectedUrls = () => Array.from(this.selectedUrls); } diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/application-table.mock.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/application-table.mock.ts index 4df363ab2c7..4dffa60b562 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/application-table.mock.ts +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/application-table.mock.ts @@ -6,6 +6,7 @@ export const applicationTableMockData = [ totalPasswords: 10, atRiskMembers: 2, totalMembers: 5, + isMarkedAsCritical: false, }, { id: 2, @@ -14,6 +15,7 @@ export const applicationTableMockData = [ totalPasswords: 8, atRiskMembers: 1, totalMembers: 3, + isMarkedAsCritical: false, }, { id: 3, @@ -22,6 +24,7 @@ export const applicationTableMockData = [ totalPasswords: 6, atRiskMembers: 0, totalMembers: 2, + isMarkedAsCritical: false, }, { id: 4, @@ -30,6 +33,7 @@ export const applicationTableMockData = [ totalPasswords: 4, atRiskMembers: 0, totalMembers: 1, + isMarkedAsCritical: false, }, { id: 5, @@ -38,6 +42,7 @@ export const applicationTableMockData = [ totalPasswords: 2, atRiskMembers: 0, totalMembers: 0, + isMarkedAsCritical: false, }, { id: 6, @@ -46,5 +51,6 @@ export const applicationTableMockData = [ totalPasswords: 1, atRiskMembers: 0, totalMembers: 0, + isMarkedAsCritical: false, }, ]; diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.html b/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.html index 7fe320ede6a..ae8bd94e5f3 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.html +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.html @@ -40,7 +40,7 @@

{{ "riskInsights" | i18n }}

- {{ "criticalApplicationsWithCount" | i18n: criticalAppsCount }} + {{ "criticalApplicationsWithCount" | i18n: (criticalApps$ | async)?.length ?? 0 }} diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.ts index 75601994c70..5adb0d32945 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.ts +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/risk-insights.component.ts @@ -6,11 +6,17 @@ import { Observable, EMPTY } from "rxjs"; import { map, switchMap } from "rxjs/operators"; import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { RiskInsightsDataService } from "@bitwarden/bit-common/tools/reports/risk-insights"; +import { + RiskInsightsDataService, + CriticalAppsService, + PasswordHealthReportApplicationsResponse, +} from "@bitwarden/bit-common/tools/reports/risk-insights"; import { ApplicationHealthReportDetail } from "@bitwarden/bit-common/tools/reports/risk-insights/models/password-health"; +// eslint-disable-next-line no-restricted-imports -- used for dependency injection import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { devFlagEnabled } from "@bitwarden/common/platform/misc/flags"; +import { OrganizationId } from "@bitwarden/common/types/guid"; import { AsyncActionsModule, ButtonModule, TabsModule } from "@bitwarden/components"; import { HeaderModule } from "@bitwarden/web-vault/app/layouts/header/header.module"; @@ -51,6 +57,7 @@ export class RiskInsightsComponent implements OnInit { dataLastUpdated: Date = new Date(); isCriticalAppsFeatureEnabled: boolean = false; + criticalApps$: Observable = new Observable(); showDebugTabs: boolean = false; appsCount: number = 0; @@ -69,10 +76,13 @@ export class RiskInsightsComponent implements OnInit { private router: Router, private configService: ConfigService, private dataService: RiskInsightsDataService, + private criticalAppsService: CriticalAppsService, ) { this.route.queryParams.pipe(takeUntilDestroyed()).subscribe(({ tabIndex }) => { this.tabIndex = !isNaN(Number(tabIndex)) ? Number(tabIndex) : RiskInsightsTabType.AllApps; }); + const orgId = this.route.snapshot.paramMap.get("organizationId") ?? ""; + this.criticalApps$ = this.criticalAppsService.getAppsListForOrg(orgId); } async ngOnInit() { @@ -104,6 +114,7 @@ export class RiskInsightsComponent implements OnInit { if (applications) { this.appsCount = applications.length; } + this.criticalAppsService.setOrganizationId(this.organizationId as OrganizationId); }, }); } From 8942f8d440f1289cf2c9a1dff94237901d9487d9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 16 Jan 2025 14:56:48 +0000 Subject: [PATCH 09/13] [deps] SM: Update lint-staged to v15.4.0 (#10565) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Oscar Hinton --- package-lock.json | 38 +++++++++++++++++++------------------- package.json | 2 +- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index b94ad3b762f..4b30fedbd06 100644 --- a/package-lock.json +++ b/package-lock.json @@ -156,7 +156,7 @@ "jest-junit": "16.0.0", "jest-mock-extended": "3.0.7", "jest-preset-angular": "14.1.1", - "lint-staged": "15.2.8", + "lint-staged": "15.4.0", "mini-css-extract-plugin": "2.9.2", "node-ipc": "9.2.1", "postcss": "8.4.49", @@ -14661,9 +14661,9 @@ } }, "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -22338,22 +22338,22 @@ "license": "MIT" }, "node_modules/lint-staged": { - "version": "15.2.8", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.8.tgz", - "integrity": "sha512-PUWFf2zQzsd9EFU+kM1d7UP+AZDbKFKuj+9JNVTBkhUFhbg4MAt6WfyMMwBfM4lYqd4D2Jwac5iuTu9rVj4zCQ==", + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.4.0.tgz", + "integrity": "sha512-UdODqEZiQimd7rCzZ2vqFuELRNUda3mdv7M93jhE4SmDiqAj/w/msvwKgagH23jv2iCPw6Q5m+ltX4VlHvp2LQ==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "~5.3.0", + "chalk": "~5.4.1", "commander": "~12.1.0", - "debug": "~4.3.6", + "debug": "~4.4.0", "execa": "~8.0.1", - "lilconfig": "~3.1.2", - "listr2": "~8.2.4", - "micromatch": "~4.0.7", + "lilconfig": "~3.1.3", + "listr2": "~8.2.5", + "micromatch": "~4.0.8", "pidtree": "~0.6.0", "string-argv": "~0.3.2", - "yaml": "~2.5.0" + "yaml": "~2.6.1" }, "bin": { "lint-staged": "bin/lint-staged.js" @@ -22366,9 +22366,9 @@ } }, "node_modules/lint-staged/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", "dev": true, "license": "MIT", "engines": { @@ -33151,9 +33151,9 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz", - "integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz", + "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==", "license": "ISC", "bin": { "yaml": "bin.mjs" diff --git a/package.json b/package.json index 07e3f217867..5d7c2ace64d 100644 --- a/package.json +++ b/package.json @@ -116,7 +116,7 @@ "jest-junit": "16.0.0", "jest-mock-extended": "3.0.7", "jest-preset-angular": "14.1.1", - "lint-staged": "15.2.8", + "lint-staged": "15.4.0", "mini-css-extract-plugin": "2.9.2", "node-ipc": "9.2.1", "postcss": "8.4.49", From cc311d9a9258e66b2af9eac8749383d8427e2da2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=9C=A8=20Audrey=20=E2=9C=A8?= Date: Thu, 16 Jan 2025 10:02:28 -0500 Subject: [PATCH 10/13] [PM-16788] introduce generator metadata (#12757) --- .../core/src/metadata/algorithm-metadata.ts | 61 +++++ .../tools/generator/core/src/metadata/data.ts | 48 ++++ .../core/src/metadata/email/catchall.spec.ts | 65 ++++++ .../core/src/metadata/email/catchall.ts | 70 ++++++ .../core/src/metadata/email/forwarder.ts | 4 + .../src/metadata/email/plus-address.spec.ts | 65 ++++++ .../core/src/metadata/email/plus-address.ts | 72 ++++++ .../core/src/metadata/generator-metadata.ts | 29 +++ .../metadata/password/eff-word-list.spec.ts | 102 ++++++++ .../src/metadata/password/eff-word-list.ts | 91 ++++++++ .../metadata/password/random-password.spec.ts | 105 +++++++++ .../src/metadata/password/random-password.ts | 117 ++++++++++ .../core/src/metadata/profile-metadata.ts | 80 +++++++ .../tools/generator/core/src/metadata/type.ts | 28 +++ .../metadata/username/eff-word-list.spec.ts | 58 +++++ .../src/metadata/username/eff-word-list.ts | 70 ++++++ .../generator/core/src/metadata/util.spec.ts | 218 ++++++++++++++++++ .../tools/generator/core/src/metadata/util.ts | 60 +++++ .../core/src/policies/catchall-constraints.ts | 2 +- .../src/types/password-generation-options.ts | 20 +- libs/tools/generator/core/src/util.ts | 2 +- 21 files changed, 1355 insertions(+), 12 deletions(-) create mode 100644 libs/tools/generator/core/src/metadata/algorithm-metadata.ts create mode 100644 libs/tools/generator/core/src/metadata/data.ts create mode 100644 libs/tools/generator/core/src/metadata/email/catchall.spec.ts create mode 100644 libs/tools/generator/core/src/metadata/email/catchall.ts create mode 100644 libs/tools/generator/core/src/metadata/email/forwarder.ts create mode 100644 libs/tools/generator/core/src/metadata/email/plus-address.spec.ts create mode 100644 libs/tools/generator/core/src/metadata/email/plus-address.ts create mode 100644 libs/tools/generator/core/src/metadata/generator-metadata.ts create mode 100644 libs/tools/generator/core/src/metadata/password/eff-word-list.spec.ts create mode 100644 libs/tools/generator/core/src/metadata/password/eff-word-list.ts create mode 100644 libs/tools/generator/core/src/metadata/password/random-password.spec.ts create mode 100644 libs/tools/generator/core/src/metadata/password/random-password.ts create mode 100644 libs/tools/generator/core/src/metadata/profile-metadata.ts create mode 100644 libs/tools/generator/core/src/metadata/type.ts create mode 100644 libs/tools/generator/core/src/metadata/username/eff-word-list.spec.ts create mode 100644 libs/tools/generator/core/src/metadata/username/eff-word-list.ts create mode 100644 libs/tools/generator/core/src/metadata/util.spec.ts create mode 100644 libs/tools/generator/core/src/metadata/util.ts diff --git a/libs/tools/generator/core/src/metadata/algorithm-metadata.ts b/libs/tools/generator/core/src/metadata/algorithm-metadata.ts new file mode 100644 index 00000000000..f776dd76e54 --- /dev/null +++ b/libs/tools/generator/core/src/metadata/algorithm-metadata.ts @@ -0,0 +1,61 @@ +import { CredentialAlgorithm, CredentialType } from "./type"; + +/** Credential generator metadata common across credential generators */ +export type AlgorithmMetadata = { + /** Uniquely identifies the credential configuration + * @example + * // Use `isForwarderIntegration(algorithm: CredentialAlgorithm)` + * // to pattern test whether the credential describes a forwarder algorithm + * const meta : AlgorithmMetadata = // ... + * const { forwarder } = isForwarderIntegration(meta.id) ? credentialId : {}; + */ + id: CredentialAlgorithm; + + /** The kind of credential generated by this configuration */ + category: CredentialType; + + /** Used to order credential algorithms for display purposes. + * Items with lesser weights appear before entries with greater + * weights (i.e. ascending sort). + */ + weight: number; + + /** Localization keys */ + i18nKeys: { + /** descriptive name of the algorithm */ + name: string; + + /** explanatory text for the algorithm */ + description?: string; + + /** labels the generate action */ + generateCredential: string; + + /** message informing users when the generator produces a new credential */ + credentialGenerated: string; + + /* labels the action that assigns a generated value to a domain object */ + useCredential: string; + + /** labels the generated output */ + credentialType: string; + + /** labels the copy output action */ + copyCredential: string; + }; + + /** fine-tunings for generator user experiences */ + capabilities: { + /** `true` when the generator supports autogeneration + * @remarks this property is useful when credential generation + * carries side effects, such as configuring a service external + * to Bitwarden. + */ + autogenerate: boolean; + + /** Well-known fields to display on the options panel or collect from the environment. + * @remarks: at present, this is only used by forwarders + */ + fields: string[]; + }; +}; diff --git a/libs/tools/generator/core/src/metadata/data.ts b/libs/tools/generator/core/src/metadata/data.ts new file mode 100644 index 00000000000..2b9dad50557 --- /dev/null +++ b/libs/tools/generator/core/src/metadata/data.ts @@ -0,0 +1,48 @@ +import { deepFreeze } from "@bitwarden/common/tools/util"; + +/** algorithms for generating credentials */ +export const Algorithm = Object.freeze({ + /** A password composed of random characters */ + password: "password", + + /** A password composed of random words from the EFF word list */ + passphrase: "passphrase", + + /** A username composed of words from the EFF word list */ + username: "username", + + /** An email username composed of random characters */ + catchall: "catchall", + + /** An email username composed of words from the EFF word list */ + plusAddress: "subaddress", +} as const); + +/** categorizes credentials according to their use-case outside of Bitwarden */ +export const Type = Object.freeze({ + password: "password", + username: "username", + email: "email", +} as const); + +/** categorizes settings according to their expected use-case within Bitwarden */ +export const Profile = Object.freeze({ + /** account-level generator options. This is the default. + * @remarks these are the options displayed on the generator tab + */ + account: "account", + + // FIXME: consider adding a profile for bitwarden's master password +}); + +/** Credential generation algorithms grouped by purpose. */ +export const AlgorithmsByType = deepFreeze({ + /** Algorithms that produce passwords */ + [Type.password]: [Algorithm.password, Algorithm.passphrase] as const, + + /** Algorithms that produce usernames */ + [Type.username]: [Algorithm.username] as const, + + /** Algorithms that produce email addresses */ + [Type.email]: [Algorithm.catchall, Algorithm.plusAddress] as const, +} as const); diff --git a/libs/tools/generator/core/src/metadata/email/catchall.spec.ts b/libs/tools/generator/core/src/metadata/email/catchall.spec.ts new file mode 100644 index 00000000000..f63f141842c --- /dev/null +++ b/libs/tools/generator/core/src/metadata/email/catchall.spec.ts @@ -0,0 +1,65 @@ +import { mock } from "jest-mock-extended"; + +import { EmailRandomizer } from "../../engine"; +import { CatchallConstraints } from "../../policies/catchall-constraints"; +import { CatchallGenerationOptions, GeneratorDependencyProvider } from "../../types"; +import { Profile } from "../data"; +import { CoreProfileMetadata } from "../profile-metadata"; +import { isCoreProfile } from "../util"; + +import catchall from "./catchall"; + +const dependencyProvider = mock(); + +describe("email - catchall generator metadata", () => { + describe("engine.create", () => { + it("returns an email randomizer", () => { + expect(catchall.engine.create(dependencyProvider)).toBeInstanceOf(EmailRandomizer); + }); + }); + + describe("profiles[account]", () => { + let accountProfile: CoreProfileMetadata = null; + beforeEach(() => { + const profile = catchall.profiles[Profile.account]; + if (isCoreProfile(profile)) { + accountProfile = profile; + } + }); + + describe("storage.options.deserializer", () => { + it("returns its input", () => { + const value: CatchallGenerationOptions = { + catchallType: "random", + catchallDomain: "example.com", + }; + + const result = accountProfile.storage.options.deserializer(value); + + expect(result).toBe(value); + }); + }); + + describe("constraints.create", () => { + // these tests check that the wiring is correct by exercising the behavior + // of functionality encapsulated by `create`. These methods may fail if the + // enclosed behaviors change. + + it("creates a catchall constraints", () => { + const context = { defaultConstraints: {} }; + + const constraints = accountProfile.constraints.create([], context); + + expect(constraints).toBeInstanceOf(CatchallConstraints); + }); + + it("extracts the domain from context.email", () => { + const context = { email: "foo@example.com", defaultConstraints: {} }; + + const constraints = accountProfile.constraints.create([], context) as CatchallConstraints; + + expect(constraints.domain).toEqual("example.com"); + }); + }); + }); +}); diff --git a/libs/tools/generator/core/src/metadata/email/catchall.ts b/libs/tools/generator/core/src/metadata/email/catchall.ts new file mode 100644 index 00000000000..0711e5c3719 --- /dev/null +++ b/libs/tools/generator/core/src/metadata/email/catchall.ts @@ -0,0 +1,70 @@ +import { GENERATOR_DISK } from "@bitwarden/common/platform/state"; +import { PublicClassifier } from "@bitwarden/common/tools/public-classifier"; +import { deepFreeze } from "@bitwarden/common/tools/util"; + +import { EmailRandomizer } from "../../engine"; +import { CatchallConstraints } from "../../policies/catchall-constraints"; +import { + CatchallGenerationOptions, + CredentialGenerator, + GeneratorDependencyProvider, +} from "../../types"; +import { Algorithm, Type, Profile } from "../data"; +import { GeneratorMetadata } from "../generator-metadata"; + +const catchall: GeneratorMetadata = deepFreeze({ + id: Algorithm.catchall, + category: Type.email, + weight: 210, + i18nKeys: { + name: "catchallEmail", + description: "catchallEmailDesc", + credentialType: "email", + generateCredential: "generateEmail", + credentialGenerated: "emailGenerated", + copyCredential: "copyEmail", + useCredential: "useThisEmail", + }, + capabilities: { + autogenerate: true, + fields: [], + }, + engine: { + create( + dependencies: GeneratorDependencyProvider, + ): CredentialGenerator { + return new EmailRandomizer(dependencies.randomizer); + }, + }, + profiles: { + [Profile.account]: { + type: "core", + storage: { + key: "catchallGeneratorSettings", + target: "object", + format: "plain", + classifier: new PublicClassifier([ + "catchallType", + "catchallDomain", + ]), + state: GENERATOR_DISK, + initial: { + catchallType: "random", + catchallDomain: "", + }, + options: { + deserializer: (value) => value, + clearOn: ["logout"], + }, + }, + constraints: { + default: { catchallDomain: { minLength: 1 } }, + create(_policies, context) { + return new CatchallConstraints(context.email ?? ""); + }, + }, + }, + }, +}); + +export default catchall; diff --git a/libs/tools/generator/core/src/metadata/email/forwarder.ts b/libs/tools/generator/core/src/metadata/email/forwarder.ts new file mode 100644 index 00000000000..1dfc219d466 --- /dev/null +++ b/libs/tools/generator/core/src/metadata/email/forwarder.ts @@ -0,0 +1,4 @@ +// Forwarders are pending integration with the extension API +// +// They use the 300-block of weights and derive their metadata +// using logic similar to `toCredentialGeneratorConfiguration` diff --git a/libs/tools/generator/core/src/metadata/email/plus-address.spec.ts b/libs/tools/generator/core/src/metadata/email/plus-address.spec.ts new file mode 100644 index 00000000000..2ac7645ed30 --- /dev/null +++ b/libs/tools/generator/core/src/metadata/email/plus-address.spec.ts @@ -0,0 +1,65 @@ +import { mock } from "jest-mock-extended"; + +import { EmailRandomizer } from "../../engine"; +import { SubaddressConstraints } from "../../policies/subaddress-constraints"; +import { SubaddressGenerationOptions, GeneratorDependencyProvider } from "../../types"; +import { Profile } from "../data"; +import { CoreProfileMetadata } from "../profile-metadata"; +import { isCoreProfile } from "../util"; + +import plusAddress from "./plus-address"; + +const dependencyProvider = mock(); + +describe("email - plus address generator metadata", () => { + describe("engine.create", () => { + it("returns an email randomizer", () => { + expect(plusAddress.engine.create(dependencyProvider)).toBeInstanceOf(EmailRandomizer); + }); + }); + + describe("profiles[account]", () => { + let accountProfile: CoreProfileMetadata = null; + beforeEach(() => { + const profile = plusAddress.profiles[Profile.account]; + if (isCoreProfile(profile)) { + accountProfile = profile; + } + }); + + describe("storage.options.deserializer", () => { + it("returns its input", () => { + const value: SubaddressGenerationOptions = { + subaddressType: "random", + subaddressEmail: "foo@example.com", + }; + + const result = accountProfile.storage.options.deserializer(value); + + expect(result).toBe(value); + }); + }); + + describe("constraints.create", () => { + // these tests check that the wiring is correct by exercising the behavior + // of functionality encapsulated by `create`. These methods may fail if the + // enclosed behaviors change. + + it("creates a subaddress constraints", () => { + const context = { defaultConstraints: {} }; + + const constraints = accountProfile.constraints.create([], context); + + expect(constraints).toBeInstanceOf(SubaddressConstraints); + }); + + it("sets the constraint email to context.email", () => { + const context = { email: "bar@example.com", defaultConstraints: {} }; + + const constraints = accountProfile.constraints.create([], context) as SubaddressConstraints; + + expect(constraints.email).toEqual("bar@example.com"); + }); + }); + }); +}); diff --git a/libs/tools/generator/core/src/metadata/email/plus-address.ts b/libs/tools/generator/core/src/metadata/email/plus-address.ts new file mode 100644 index 00000000000..0db0acd415c --- /dev/null +++ b/libs/tools/generator/core/src/metadata/email/plus-address.ts @@ -0,0 +1,72 @@ +import { GENERATOR_DISK } from "@bitwarden/common/platform/state"; +import { PublicClassifier } from "@bitwarden/common/tools/public-classifier"; +import { deepFreeze } from "@bitwarden/common/tools/util"; + +import { EmailRandomizer } from "../../engine"; +import { SubaddressConstraints } from "../../policies/subaddress-constraints"; +import { + CredentialGenerator, + GeneratorDependencyProvider, + SubaddressGenerationOptions, +} from "../../types"; +import { Algorithm, Profile, Type } from "../data"; +import { GeneratorMetadata } from "../generator-metadata"; + +const plusAddress: GeneratorMetadata = deepFreeze({ + id: Algorithm.plusAddress, + category: Type.email, + weight: 200, + i18nKeys: { + name: "plusAddressedEmail", + description: "plusAddressedEmailDesc", + credentialType: "email", + generateCredential: "generateEmail", + credentialGenerated: "emailGenerated", + copyCredential: "copyEmail", + useCredential: "useThisEmail", + }, + capabilities: { + autogenerate: true, + fields: [], + }, + engine: { + create( + dependencies: GeneratorDependencyProvider, + ): CredentialGenerator { + return new EmailRandomizer(dependencies.randomizer); + }, + }, + profiles: { + [Profile.account]: { + type: "core", + storage: { + key: "subaddressGeneratorSettings", + target: "object", + format: "plain", + classifier: new PublicClassifier([ + "subaddressType", + "subaddressEmail", + ]), + state: GENERATOR_DISK, + initial: { + subaddressType: "random", + subaddressEmail: "", + }, + options: { + deserializer(value) { + return value; + }, + clearOn: ["logout"], + }, + }, + constraints: { + default: {}, + create(_policy, context) { + return new SubaddressConstraints(context.email ?? ""); + }, + }, + }, + }, +}); + +export default plusAddress; diff --git a/libs/tools/generator/core/src/metadata/generator-metadata.ts b/libs/tools/generator/core/src/metadata/generator-metadata.ts new file mode 100644 index 00000000000..9296d30430e --- /dev/null +++ b/libs/tools/generator/core/src/metadata/generator-metadata.ts @@ -0,0 +1,29 @@ +import { CredentialGenerator, GeneratorDependencyProvider } from "../types"; + +import { AlgorithmMetadata } from "./algorithm-metadata"; +import { Profile } from "./data"; +import { ProfileMetadata } from "./profile-metadata"; + +/** Extends the algorithm metadata with storage and engine configurations. + * @example + * // Use `isForwarderIntegration(algorithm: CredentialAlgorithm)` + * // to pattern test whether the credential describes a forwarder algorithm + * const meta : CredentialGeneratorInfo = // ... + * const { forwarder } = isForwarderIntegration(meta.id) ? credentialId : {}; + */ +export type GeneratorMetadata = AlgorithmMetadata & { + /** An algorithm that generates credentials when ran. */ + engine: { + /** Factory for the generator + */ + create: (randomizer: GeneratorDependencyProvider) => CredentialGenerator; + }; + + /** Defines parameters for credential generation */ + profiles: { + /** profiles supported by this generator; when `undefined`, + * the generator does not support the profile. + */ + [K in keyof typeof Profile]?: ProfileMetadata; + }; +}; diff --git a/libs/tools/generator/core/src/metadata/password/eff-word-list.spec.ts b/libs/tools/generator/core/src/metadata/password/eff-word-list.spec.ts new file mode 100644 index 00000000000..57961a60033 --- /dev/null +++ b/libs/tools/generator/core/src/metadata/password/eff-word-list.spec.ts @@ -0,0 +1,102 @@ +import { mock } from "jest-mock-extended"; + +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; + +import { PasswordRandomizer } from "../../engine"; +import { PassphrasePolicyConstraints } from "../../policies"; +import { PassphraseGenerationOptions, GeneratorDependencyProvider } from "../../types"; +import { Profile } from "../data"; +import { CoreProfileMetadata } from "../profile-metadata"; +import { isCoreProfile } from "../util"; + +import effPassphrase from "./eff-word-list"; + +const dependencyProvider = mock(); + +describe("password - eff words generator metadata", () => { + describe("engine.create", () => { + it("returns an email randomizer", () => { + expect(effPassphrase.engine.create(dependencyProvider)).toBeInstanceOf(PasswordRandomizer); + }); + }); + + describe("profiles[account]", () => { + let accountProfile: CoreProfileMetadata = null; + beforeEach(() => { + const profile = effPassphrase.profiles[Profile.account]; + if (isCoreProfile(profile)) { + accountProfile = profile; + } + }); + + describe("storage.options.deserializer", () => { + it("returns its input", () => { + const value: PassphraseGenerationOptions = { ...accountProfile.storage.initial }; + + const result = accountProfile.storage.options.deserializer(value); + + expect(result).toBe(value); + }); + }); + + describe("constraints.create", () => { + // these tests check that the wiring is correct by exercising the behavior + // of functionality encapsulated by `create`. These methods may fail if the + // enclosed behaviors change. + + it("creates a passphrase policy constraints", () => { + const context = { defaultConstraints: accountProfile.constraints.default }; + + const constraints = accountProfile.constraints.create([], context); + + expect(constraints).toBeInstanceOf(PassphrasePolicyConstraints); + }); + + it("forwards the policy to the constraints", () => { + const context = { defaultConstraints: accountProfile.constraints.default }; + const policies = [ + { + type: PolicyType.PasswordGenerator, + data: { + minNumberWords: 6, + capitalize: false, + includeNumber: false, + }, + }, + ] as Policy[]; + + const constraints = accountProfile.constraints.create(policies, context); + + expect(constraints.constraints.numWords.min).toEqual(6); + }); + + it("combines multiple policies in the constraints", () => { + const context = { defaultConstraints: accountProfile.constraints.default }; + const policies = [ + { + type: PolicyType.PasswordGenerator, + data: { + minNumberWords: 6, + capitalize: false, + includeNumber: false, + }, + }, + { + type: PolicyType.PasswordGenerator, + data: { + minNumberWords: 3, + capitalize: true, + includeNumber: false, + }, + }, + ] as Policy[]; + + const constraints = accountProfile.constraints.create(policies, context); + + expect(constraints.constraints.numWords.min).toEqual(6); + expect(constraints.constraints.capitalize.requiredValue).toEqual(true); + }); + }); + }); +}); diff --git a/libs/tools/generator/core/src/metadata/password/eff-word-list.ts b/libs/tools/generator/core/src/metadata/password/eff-word-list.ts new file mode 100644 index 00000000000..fc86032bf6b --- /dev/null +++ b/libs/tools/generator/core/src/metadata/password/eff-word-list.ts @@ -0,0 +1,91 @@ +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { GENERATOR_DISK } from "@bitwarden/common/platform/state"; +import { PublicClassifier } from "@bitwarden/common/tools/public-classifier"; +import { ObjectKey } from "@bitwarden/common/tools/state/object-key"; + +import { PasswordRandomizer } from "../../engine"; +import { passphraseLeastPrivilege, PassphrasePolicyConstraints } from "../../policies"; +import { + CredentialGenerator, + GeneratorDependencyProvider, + PassphraseGenerationOptions, +} from "../../types"; +import { Algorithm, Profile, Type } from "../data"; +import { GeneratorMetadata } from "../generator-metadata"; + +const passphrase: GeneratorMetadata = { + id: Algorithm.passphrase, + category: Type.password, + weight: 110, + i18nKeys: { + name: "passphrase", + credentialType: "passphrase", + generateCredential: "generatePassphrase", + credentialGenerated: "passphraseGenerated", + copyCredential: "copyPassphrase", + useCredential: "useThisPassphrase", + }, + capabilities: { + autogenerate: false, + fields: [], + }, + engine: { + create( + dependencies: GeneratorDependencyProvider, + ): CredentialGenerator { + return new PasswordRandomizer(dependencies.randomizer); + }, + }, + profiles: { + [Profile.account]: { + type: "core", + storage: { + key: "passphraseGeneratorSettings", + target: "object", + format: "plain", + classifier: new PublicClassifier([ + "numWords", + "wordSeparator", + "capitalize", + "includeNumber", + ]), + state: GENERATOR_DISK, + initial: { + numWords: 6, + wordSeparator: "-", + capitalize: false, + includeNumber: false, + }, + options: { + deserializer(value) { + return value; + }, + clearOn: ["logout"], + }, + } satisfies ObjectKey, + constraints: { + type: PolicyType.PasswordGenerator, + default: { + wordSeparator: { maxLength: 1 }, + numWords: { + min: 3, + max: 20, + recommendation: 6, + }, + }, + create(policies, context) { + const initial = { + minNumberWords: 0, + capitalize: false, + includeNumber: false, + }; + const policy = policies.reduce(passphraseLeastPrivilege, initial); + const constraints = new PassphrasePolicyConstraints(policy, context.defaultConstraints); + return constraints; + }, + }, + }, + }, +}; + +export default passphrase; diff --git a/libs/tools/generator/core/src/metadata/password/random-password.spec.ts b/libs/tools/generator/core/src/metadata/password/random-password.spec.ts new file mode 100644 index 00000000000..d91ceaac248 --- /dev/null +++ b/libs/tools/generator/core/src/metadata/password/random-password.spec.ts @@ -0,0 +1,105 @@ +import { mock } from "jest-mock-extended"; + +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; + +import { PasswordRandomizer } from "../../engine"; +import { DynamicPasswordPolicyConstraints } from "../../policies"; +import { PasswordGenerationOptions, GeneratorDependencyProvider } from "../../types"; +import { Profile } from "../data"; +import { CoreProfileMetadata } from "../profile-metadata"; +import { isCoreProfile } from "../util"; + +import password from "./random-password"; + +const dependencyProvider = mock(); + +describe("password - characters generator metadata", () => { + describe("engine.create", () => { + it("returns an email randomizer", () => { + expect(password.engine.create(dependencyProvider)).toBeInstanceOf(PasswordRandomizer); + }); + }); + + describe("profiles[account]", () => { + let accountProfile: CoreProfileMetadata = null; + beforeEach(() => { + const profile = password.profiles[Profile.account]; + if (isCoreProfile(profile)) { + accountProfile = profile; + } + }); + + describe("storage.options.deserializer", () => { + it("returns its input", () => { + const value: PasswordGenerationOptions = { ...accountProfile.storage.initial }; + + const result = accountProfile.storage.options.deserializer(value); + + expect(result).toBe(value); + }); + }); + + describe("constraints.create", () => { + // these tests check that the wiring is correct by exercising the behavior + // of functionality encapsulated by `create`. These methods may fail if the + // enclosed behaviors change. + + it("creates a passphrase policy constraints", () => { + const context = { defaultConstraints: accountProfile.constraints.default }; + + const constraints = accountProfile.constraints.create([], context); + + expect(constraints).toBeInstanceOf(DynamicPasswordPolicyConstraints); + }); + + it("forwards the policy to the constraints", () => { + const context = { defaultConstraints: accountProfile.constraints.default }; + const policies = [ + { + type: PolicyType.PasswordGenerator, + enabled: true, + data: { + minLength: 10, + capitalize: false, + useNumbers: false, + }, + }, + ] as Policy[]; + + const constraints = accountProfile.constraints.create(policies, context); + + expect(constraints.constraints.length.min).toEqual(10); + }); + + it("combines multiple policies in the constraints", () => { + const context = { defaultConstraints: accountProfile.constraints.default }; + const policies = [ + { + type: PolicyType.PasswordGenerator, + enabled: true, + data: { + minLength: 14, + useSpecial: false, + useNumbers: false, + }, + }, + { + type: PolicyType.PasswordGenerator, + enabled: true, + data: { + minLength: 10, + useSpecial: true, + includeNumber: false, + }, + }, + ] as Policy[]; + + const constraints = accountProfile.constraints.create(policies, context); + + expect(constraints.constraints.length.min).toEqual(14); + expect(constraints.constraints.special.requiredValue).toEqual(true); + }); + }); + }); +}); diff --git a/libs/tools/generator/core/src/metadata/password/random-password.ts b/libs/tools/generator/core/src/metadata/password/random-password.ts new file mode 100644 index 00000000000..693236b0967 --- /dev/null +++ b/libs/tools/generator/core/src/metadata/password/random-password.ts @@ -0,0 +1,117 @@ +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { GENERATOR_DISK } from "@bitwarden/common/platform/state"; +import { PublicClassifier } from "@bitwarden/common/tools/public-classifier"; +import { deepFreeze } from "@bitwarden/common/tools/util"; + +import { PasswordRandomizer } from "../../engine"; +import { DynamicPasswordPolicyConstraints, passwordLeastPrivilege } from "../../policies"; +import { + CredentialGenerator, + GeneratorDependencyProvider, + PasswordGeneratorSettings, +} from "../../types"; +import { Algorithm, Profile, Type } from "../data"; +import { GeneratorMetadata } from "../generator-metadata"; + +const password: GeneratorMetadata = deepFreeze({ + id: Algorithm.password, + category: Type.password, + weight: 100, + i18nKeys: { + name: "password", + generateCredential: "generatePassword", + credentialGenerated: "passwordGenerated", + credentialType: "password", + copyCredential: "copyPassword", + useCredential: "useThisPassword", + }, + capabilities: { + autogenerate: true, + fields: [], + }, + engine: { + create( + dependencies: GeneratorDependencyProvider, + ): CredentialGenerator { + return new PasswordRandomizer(dependencies.randomizer); + }, + }, + profiles: { + [Profile.account]: { + type: "core", + storage: { + key: "passwordGeneratorSettings", + target: "object", + format: "plain", + classifier: new PublicClassifier([ + "length", + "ambiguous", + "uppercase", + "minUppercase", + "lowercase", + "minLowercase", + "number", + "minNumber", + "special", + "minSpecial", + ]), + state: GENERATOR_DISK, + initial: { + length: 14, + ambiguous: true, + uppercase: true, + minUppercase: 1, + lowercase: true, + minLowercase: 1, + number: true, + minNumber: 1, + special: false, + minSpecial: 0, + }, + options: { + deserializer(value) { + return value; + }, + clearOn: ["logout"], + }, + }, + constraints: { + type: PolicyType.PasswordGenerator, + default: { + length: { + min: 5, + max: 128, + recommendation: 14, + }, + minNumber: { + min: 0, + max: 9, + }, + minSpecial: { + min: 0, + max: 9, + }, + }, + create(policies, context) { + const initial = { + minLength: 0, + useUppercase: false, + useLowercase: false, + useNumbers: false, + numberCount: 0, + useSpecial: false, + specialCount: 0, + }; + const policy = policies.reduce(passwordLeastPrivilege, initial); + const constraints = new DynamicPasswordPolicyConstraints( + policy, + context.defaultConstraints, + ); + return constraints; + }, + }, + }, + }, +}); + +export default password; diff --git a/libs/tools/generator/core/src/metadata/profile-metadata.ts b/libs/tools/generator/core/src/metadata/profile-metadata.ts new file mode 100644 index 00000000000..4ac9139f632 --- /dev/null +++ b/libs/tools/generator/core/src/metadata/profile-metadata.ts @@ -0,0 +1,80 @@ +import { PolicyType } from "@bitwarden/common/admin-console/enums"; +import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; +import { SiteId } from "@bitwarden/common/tools/extension"; +import { ObjectKey } from "@bitwarden/common/tools/state/object-key"; +import { Constraints } from "@bitwarden/common/tools/types"; + +import { GeneratorConstraints } from "../types"; + +export type ProfileContext = { + /** The email address for the current user; + * `undefined` when no email is available. + */ + email?: string; + + /** Default application limits for the profile */ + defaultConstraints: Constraints; +}; + +type ProfileConstraints = { + /** The key used to locate this profile's policies in the admin console. + * When this type is undefined, no policy is defined for the profile. + */ + type?: PolicyType; + + /** default application limits for this profile; these are overridden + * by the policy + */ + default: Constraints; + + /** Constructs generator constraints from a policy. + * @param policies the administrative policy to apply to the provided constraints + * When `type` is undefined then `policy` is `undefined` this is an empty array. + * @param defaultConstraints application constraints; typically those defined in + * the `default` member, above. + * @returns the generator constraints to apply to this profile's options. + */ + create: (policies: Policy[], context: ProfileContext) => GeneratorConstraints; +}; + +/** Generator profiles partition generator operations + * according to where they're used within the password + * manager. Core profiles store their data using the + * generator's system storage. + */ +export type CoreProfileMetadata = { + /** distinguishes profile metadata types */ + type: "core"; + + /** plaintext import buffer */ + import?: ObjectKey, Options> & { format: "plain" }; + + /** persistent storage location */ + storage: ObjectKey; + + /** policy enforced when saving the options */ + constraints: ProfileConstraints; +}; + +/** Generator profiles partition generator operations + * according to where they're used within the password + * manager. Extension profiles store their data + * using the extension system. + */ +export type ExtensionProfileMetadata = { + /** distinguishes profile metadata types */ + type: "extension"; + + /** The extension site described by this metadata */ + site: Site; + + constraints: ProfileConstraints; +}; + +/** Generator profiles partition generator operations + * according to where they're used within the password + * manager + */ +export type ProfileMetadata = + | CoreProfileMetadata + | ExtensionProfileMetadata; diff --git a/libs/tools/generator/core/src/metadata/type.ts b/libs/tools/generator/core/src/metadata/type.ts new file mode 100644 index 00000000000..924b92883e5 --- /dev/null +++ b/libs/tools/generator/core/src/metadata/type.ts @@ -0,0 +1,28 @@ +import { VendorId } from "@bitwarden/common/tools/extension"; + +import { AlgorithmsByType, Profile, Type } from "./data"; + +/** categorizes credentials according to their use-case outside of Bitwarden */ +export type CredentialType = keyof typeof Type; + +/** categorizes credentials according to their expected use-case within Bitwarden */ +export type GeneratorProfile = keyof typeof Profile; + +/** A type of password that may be generated by the credential generator. */ +export type PasswordAlgorithm = (typeof AlgorithmsByType.password)[number]; + +/** A type of username that may be generated by the credential generator. */ +export type UsernameAlgorithm = (typeof AlgorithmsByType.username)[number]; + +/** A type of email address that may be generated by the credential generator. */ +export type EmailAlgorithm = (typeof AlgorithmsByType.email)[number] | ForwarderExtensionId; + +/** Identifies a forwarding service */ +export type ForwarderExtensionId = { forwarder: VendorId }; + +/** A type of credential that can be generated by the credential generator. */ +// this is defined in terms of `AlgorithmsByType` to typecheck the keys of +// `AlgorithmsByType` against the keys of `CredentialType`. +export type CredentialAlgorithm = + | (typeof AlgorithmsByType)[CredentialType][number] + | ForwarderExtensionId; diff --git a/libs/tools/generator/core/src/metadata/username/eff-word-list.spec.ts b/libs/tools/generator/core/src/metadata/username/eff-word-list.spec.ts new file mode 100644 index 00000000000..aba9680a448 --- /dev/null +++ b/libs/tools/generator/core/src/metadata/username/eff-word-list.spec.ts @@ -0,0 +1,58 @@ +import { mock } from "jest-mock-extended"; + +import { IdentityConstraint } from "@bitwarden/common/tools/state/identity-state-constraint"; + +import { UsernameRandomizer } from "../../engine"; +import { EffUsernameGenerationOptions, GeneratorDependencyProvider } from "../../types"; +import { Profile } from "../data"; +import { CoreProfileMetadata } from "../profile-metadata"; +import { isCoreProfile } from "../util"; + +import effWordList from "./eff-word-list"; + +const dependencyProvider = mock(); + +describe("username - eff words generator metadata", () => { + describe("engine.create", () => { + it("returns an email randomizer", () => { + expect(effWordList.engine.create(dependencyProvider)).toBeInstanceOf(UsernameRandomizer); + }); + }); + + describe("profiles[account]", () => { + let accountProfile: CoreProfileMetadata = null; + beforeEach(() => { + const profile = effWordList.profiles[Profile.account]; + if (isCoreProfile(profile)) { + accountProfile = profile; + } + }); + + describe("storage.options.deserializer", () => { + it("returns its input", () => { + const value: EffUsernameGenerationOptions = { + wordCapitalize: true, + wordIncludeNumber: true, + }; + + const result = accountProfile.storage.options.deserializer(value); + + expect(result).toBe(value); + }); + }); + + describe("constraints.create", () => { + // these tests check that the wiring is correct by exercising the behavior + // of functionality encapsulated by `create`. These methods may fail if the + // enclosed behaviors change. + + it("creates a effWordList constraints", () => { + const context = { defaultConstraints: {} }; + + const constraints = accountProfile.constraints.create([], context); + + expect(constraints).toBeInstanceOf(IdentityConstraint); + }); + }); + }); +}); diff --git a/libs/tools/generator/core/src/metadata/username/eff-word-list.ts b/libs/tools/generator/core/src/metadata/username/eff-word-list.ts new file mode 100644 index 00000000000..6373daf8ed5 --- /dev/null +++ b/libs/tools/generator/core/src/metadata/username/eff-word-list.ts @@ -0,0 +1,70 @@ +import { GENERATOR_DISK } from "@bitwarden/common/platform/state"; +import { PublicClassifier } from "@bitwarden/common/tools/public-classifier"; +import { IdentityConstraint } from "@bitwarden/common/tools/state/identity-state-constraint"; +import { deepFreeze } from "@bitwarden/common/tools/util"; + +import { UsernameRandomizer } from "../../engine"; +import { + CredentialGenerator, + EffUsernameGenerationOptions, + GeneratorDependencyProvider, +} from "../../types"; +import { Algorithm, Profile, Type } from "../data"; +import { GeneratorMetadata } from "../generator-metadata"; + +const effWordList: GeneratorMetadata = deepFreeze({ + id: Algorithm.username, + category: Type.username, + weight: 400, + i18nKeys: { + name: "randomWord", + credentialType: "username", + generateCredential: "generateUsername", + credentialGenerated: "usernameGenerated", + copyCredential: "copyUsername", + useCredential: "useThisUsername", + }, + capabilities: { + autogenerate: true, + fields: [], + }, + engine: { + create( + dependencies: GeneratorDependencyProvider, + ): CredentialGenerator { + return new UsernameRandomizer(dependencies.randomizer); + }, + }, + profiles: { + [Profile.account]: { + type: "core", + storage: { + key: "effUsernameGeneratorSettings", + target: "object", + format: "plain", + classifier: new PublicClassifier([ + "wordCapitalize", + "wordIncludeNumber", + ]), + state: GENERATOR_DISK, + initial: { + wordCapitalize: false, + wordIncludeNumber: false, + website: null, + }, + options: { + deserializer: (value) => value, + clearOn: ["logout"], + }, + }, + constraints: { + default: {}, + create(_policies, _context) { + return new IdentityConstraint(); + }, + }, + }, + }, +}); + +export default effWordList; diff --git a/libs/tools/generator/core/src/metadata/util.spec.ts b/libs/tools/generator/core/src/metadata/util.spec.ts new file mode 100644 index 00000000000..2283699140b --- /dev/null +++ b/libs/tools/generator/core/src/metadata/util.spec.ts @@ -0,0 +1,218 @@ +import { VendorId } from "@bitwarden/common/tools/extension"; + +import { Algorithm, AlgorithmsByType } from "./data"; +import { ProfileMetadata } from "./profile-metadata"; +import { + isPasswordAlgorithm, + isUsernameAlgorithm, + isForwarderExtensionId, + isEmailAlgorithm, + isSameAlgorithm, + isCoreProfile, + isForwarderProfile, +} from "./util"; + +describe("credential generator metadata utility functions", () => { + describe("isPasswordAlgorithm", () => { + it("returns `true` when the algorithm is a password algorithm", () => { + for (const algorithm of AlgorithmsByType.password) { + expect(isPasswordAlgorithm(algorithm)).toBe(true); + } + }); + + it("returns `false` when the algorithm is an email algorithm", () => { + for (const algorithm of AlgorithmsByType.email) { + expect(isPasswordAlgorithm(algorithm)).toBe(false); + } + }); + + it("returns `false` when the algorithm is a username algorithm", () => { + for (const algorithm of AlgorithmsByType.username) { + expect(isPasswordAlgorithm(algorithm)).toBe(false); + } + }); + + it("returns `false` when the algorithm is a forwarder extension", () => { + expect(isPasswordAlgorithm({ forwarder: "bitwarden" as VendorId })).toBe(false); + }); + }); + + describe("isUsernameAlgorithm", () => { + it("returns `false` when the algorithm is a password algorithm", () => { + for (const algorithm of AlgorithmsByType.password) { + expect(isUsernameAlgorithm(algorithm)).toBe(false); + } + }); + + it("returns `false` when the algorithm is an email algorithm", () => { + for (const algorithm of AlgorithmsByType.email) { + expect(isUsernameAlgorithm(algorithm)).toBe(false); + } + }); + + it("returns `true` when the algorithm is a username algorithm", () => { + for (const algorithm of AlgorithmsByType.username) { + expect(isUsernameAlgorithm(algorithm)).toBe(true); + } + }); + + it("returns `false` when the algorithm is a forwarder extension", () => { + expect(isUsernameAlgorithm({ forwarder: "bitwarden" as VendorId })).toBe(false); + }); + }); + + describe("isForwarderExtensionId", () => { + it("returns `false` when the algorithm is a password algorithm", () => { + for (const algorithm of AlgorithmsByType.password) { + expect(isForwarderExtensionId(algorithm)).toBe(false); + } + }); + + it("returns `false` when the algorithm is an email algorithm", () => { + for (const algorithm of AlgorithmsByType.email) { + expect(isForwarderExtensionId(algorithm)).toBe(false); + } + }); + + it("returns `false` when the algorithm is a username algorithm", () => { + for (const algorithm of AlgorithmsByType.username) { + expect(isForwarderExtensionId(algorithm)).toBe(false); + } + }); + + it("returns `true` when the algorithm is a forwarder extension", () => { + expect(isForwarderExtensionId({ forwarder: "bitwarden" as VendorId })).toBe(true); + }); + }); + + describe("isEmailAlgorithm", () => { + it("returns `false` when the algorithm is a password algorithm", () => { + for (const algorithm of AlgorithmsByType.password) { + expect(isEmailAlgorithm(algorithm)).toBe(false); + } + }); + + it("returns `true` when the algorithm is an email algorithm", () => { + for (const algorithm of AlgorithmsByType.email) { + expect(isEmailAlgorithm(algorithm)).toBe(true); + } + }); + + it("returns `false` when the algorithm is a username algorithm", () => { + for (const algorithm of AlgorithmsByType.username) { + expect(isEmailAlgorithm(algorithm)).toBe(false); + } + }); + + it("returns `true` when the algorithm is a forwarder extension", () => { + expect(isEmailAlgorithm({ forwarder: "bitwarden" as VendorId })).toBe(true); + }); + }); + + describe("isSameAlgorithm", () => { + it("returns `true` when the algorithms are equal", () => { + // identical object + expect(isSameAlgorithm(Algorithm.catchall, Algorithm.catchall)).toBe(true); + + // equal object + expect(isSameAlgorithm(Algorithm.catchall, `${Algorithm.catchall}`)).toBe(true); + }); + + it("returns `false` when the algorithms are different", () => { + // not an exhaustive list + expect(isSameAlgorithm(Algorithm.catchall, Algorithm.passphrase)).toBe(false); + expect(isSameAlgorithm(Algorithm.passphrase, Algorithm.password)).toBe(false); + expect(isSameAlgorithm(Algorithm.password, Algorithm.plusAddress)).toBe(false); + expect(isSameAlgorithm(Algorithm.plusAddress, Algorithm.username)).toBe(false); + expect(isSameAlgorithm(Algorithm.username, Algorithm.passphrase)).toBe(false); + }); + + it("returns `true` when the algorithms refer to a forwarder with a matching vendor", () => { + const someVendor = { forwarder: "bitwarden" as VendorId }; + const sameVendor = { forwarder: "bitwarden" as VendorId }; + expect(isSameAlgorithm(someVendor, sameVendor)).toBe(true); + }); + + it("returns `false` when the algorithms refer to a forwarder with a different vendor", () => { + const someVendor = { forwarder: "bitwarden" as VendorId }; + const sameVendor = { forwarder: "bytewarden" as VendorId }; + expect(isSameAlgorithm(someVendor, sameVendor)).toBe(false); + }); + + it("returns `false` when the algorithms refer to a forwarder and a core algorithm", () => { + const someVendor = { forwarder: "bitwarden" as VendorId }; + // not an exhaustive list + expect(isSameAlgorithm(someVendor, Algorithm.plusAddress)).toBe(false); + expect(isSameAlgorithm(Algorithm.username, someVendor)).toBe(false); + }); + }); + + describe("isCoreProfile", () => { + it("returns `true` when the profile's type is `core`", () => { + const profile: ProfileMetadata = { + type: "core", + storage: null, + constraints: { + default: {}, + create: () => null, + }, + }; + + expect(isCoreProfile(profile)).toBe(true); + }); + + it("returns `false` when the profile's type is `extension`", () => { + const profile: ProfileMetadata = { + type: "extension", + site: "forwarder", + constraints: { + default: {}, + create: () => null, + }, + }; + + expect(isCoreProfile(profile)).toBe(false); + }); + }); + + describe("isForwarderProfile", () => { + it("returns `false` when the profile's type is `core`", () => { + const profile: ProfileMetadata = { + type: "core", + storage: null, + constraints: { + default: {}, + create: () => null, + }, + }; + + expect(isForwarderProfile(profile)).toBe(false); + }); + + it("returns `true` when the profile's type is `extension` and the site is `forwarder`", () => { + const profile: ProfileMetadata = { + type: "extension", + site: "forwarder", + constraints: { + default: {}, + create: () => null, + }, + }; + + expect(isForwarderProfile(profile)).toBe(true); + }); + + it("returns `false` when the profile's type is `extension` and the site is not `forwarder`", () => { + const profile: ProfileMetadata = { + type: "extension", + site: "not-a-forwarder" as any, + constraints: { + default: {}, + create: () => null, + }, + }; + + expect(isForwarderProfile(profile)).toBe(false); + }); + }); +}); diff --git a/libs/tools/generator/core/src/metadata/util.ts b/libs/tools/generator/core/src/metadata/util.ts new file mode 100644 index 00000000000..e85061720ad --- /dev/null +++ b/libs/tools/generator/core/src/metadata/util.ts @@ -0,0 +1,60 @@ +import { AlgorithmsByType } from "./data"; +import { CoreProfileMetadata, ExtensionProfileMetadata, ProfileMetadata } from "./profile-metadata"; +import { + CredentialAlgorithm, + EmailAlgorithm, + ForwarderExtensionId, + PasswordAlgorithm, + UsernameAlgorithm, +} from "./type"; + +/** Returns true when the input algorithm is a password algorithm. */ +export function isPasswordAlgorithm( + algorithm: CredentialAlgorithm, +): algorithm is PasswordAlgorithm { + return AlgorithmsByType.password.includes(algorithm as any); +} + +/** Returns true when the input algorithm is a username algorithm. */ +export function isUsernameAlgorithm( + algorithm: CredentialAlgorithm, +): algorithm is UsernameAlgorithm { + return AlgorithmsByType.username.includes(algorithm as any); +} + +/** Returns true when the input algorithm is a forwarder integration. */ +export function isForwarderExtensionId( + algorithm: CredentialAlgorithm, +): algorithm is ForwarderExtensionId { + return algorithm && typeof algorithm === "object" && "forwarder" in algorithm; +} + +/** Returns true when the input algorithm is an email algorithm. */ +export function isEmailAlgorithm(algorithm: CredentialAlgorithm): algorithm is EmailAlgorithm { + return AlgorithmsByType.email.includes(algorithm as any) || isForwarderExtensionId(algorithm); +} + +/** Returns true when the algorithms are the same. */ +export function isSameAlgorithm(lhs: CredentialAlgorithm, rhs: CredentialAlgorithm) { + if (lhs === rhs) { + return true; + } else if (isForwarderExtensionId(lhs) && isForwarderExtensionId(rhs)) { + return lhs.forwarder === rhs.forwarder; + } else { + return false; + } +} + +/** Returns true when the input describes a core profile. */ +export function isCoreProfile( + value: ProfileMetadata, +): value is CoreProfileMetadata { + return value.type === "core"; +} + +/** Returns true when the input describes a forwarder extension profile. */ +export function isForwarderProfile( + value: ProfileMetadata, +): value is ExtensionProfileMetadata { + return value.type === "extension" && value.site === "forwarder"; +} diff --git a/libs/tools/generator/core/src/policies/catchall-constraints.ts b/libs/tools/generator/core/src/policies/catchall-constraints.ts index 47476a304a9..7793180988d 100644 --- a/libs/tools/generator/core/src/policies/catchall-constraints.ts +++ b/libs/tools/generator/core/src/policies/catchall-constraints.ts @@ -24,7 +24,7 @@ export class CatchallConstraints implements StateConstraints> = {}; diff --git a/libs/tools/generator/core/src/types/password-generation-options.ts b/libs/tools/generator/core/src/types/password-generation-options.ts index 76e8827d4de..7a8a538c409 100644 --- a/libs/tools/generator/core/src/types/password-generation-options.ts +++ b/libs/tools/generator/core/src/types/password-generation-options.ts @@ -2,58 +2,58 @@ */ export type PasswordGeneratorSettings = { /** The length of the password selected by the user */ - length: number; + length?: number; /** `true` when ambiguous characters may be included in the output. * `false` when ambiguous characters should not be included in the output. */ - ambiguous: boolean; + ambiguous?: boolean; /** `true` when uppercase ASCII characters should be included in the output * This value defaults to `false. */ - uppercase: boolean; + uppercase?: boolean; /** The minimum number of uppercase characters to include in the output. * The value is ignored when `uppercase` is `false`. * The value defaults to 1 when `uppercase` is `true`. */ - minUppercase: number; + minUppercase?: number; /** `true` when lowercase ASCII characters should be included in the output. * This value defaults to `false`. */ - lowercase: boolean; + lowercase?: boolean; /** The minimum number of lowercase characters to include in the output. * The value defaults to 1 when `lowercase` is `true`. * The value defaults to 0 when `lowercase` is `false`. */ - minLowercase: number; + minLowercase?: number; /** Whether or not to include ASCII digits in the output * This value defaults to `true` when `minNumber` is at least 1. * This value defaults to `false` when `minNumber` is less than 1. */ - number: boolean; + number?: boolean; /** The minimum number of digits to include in the output. * The value defaults to 1 when `number` is `true`. * The value defaults to 0 when `number` is `false`. */ - minNumber: number; + minNumber?: number; /** Whether or not to include special characters in the output. * This value defaults to `true` when `minSpecial` is at least 1. * This value defaults to `false` when `minSpecial` is less than 1. */ - special: boolean; + special?: boolean; /** The minimum number of special characters to include in the output. * This value defaults to 1 when `special` is `true`. * This value defaults to 0 when `special` is `false`. */ - minSpecial: number; + minSpecial?: number; }; /** Request format for password credential generation. diff --git a/libs/tools/generator/core/src/util.ts b/libs/tools/generator/core/src/util.ts index 98c2e8ab283..4b6041ffeba 100644 --- a/libs/tools/generator/core/src/util.ts +++ b/libs/tools/generator/core/src/util.ts @@ -107,7 +107,7 @@ export function optionsToRandomAsciiRequest(options: PasswordGenerationOptions) DefaultPasswordGenerationOptions.special, DefaultPasswordGenerationOptions.minSpecial, ), - ambiguous: options.ambiguous ?? DefaultPasswordGenerationOptions.ambiguous, + ambiguous: options.ambiguous ?? DefaultPasswordGenerationOptions.ambiguous!, all: 0, }; From 0fce8e2726e5b5665b573c45cb5219ab9c55131a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 16 Jan 2025 16:05:44 +0100 Subject: [PATCH 11/13] [deps] SM: Update eslint-plugin-storybook to v0.11.2 (#11322) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Oscar Hinton --- package-lock.json | 282 ++++++++++------------------------------------ package.json | 2 +- 2 files changed, 62 insertions(+), 222 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4b30fedbd06..bf724bae691 100644 --- a/package-lock.json +++ b/package-lock.json @@ -146,7 +146,7 @@ "eslint-plugin-import": "2.29.1", "eslint-plugin-rxjs": "5.0.3", "eslint-plugin-rxjs-angular": "2.0.1", - "eslint-plugin-storybook": "0.8.0", + "eslint-plugin-storybook": "0.11.2", "eslint-plugin-tailwindcss": "3.17.5", "html-loader": "5.1.0", "html-webpack-injector": "1.1.4", @@ -10210,19 +10210,6 @@ "typescript": ">=4.8.4 <5.8.0" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ts-api-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz", - "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, "node_modules/@typescript-eslint/experimental-utils": { "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.62.0.tgz", @@ -10456,56 +10443,42 @@ "typescript": ">=4.8.4 <5.8.0" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/ts-api-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz", - "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, "node_modules/@typescript-eslint/utils": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.11.0.tgz", - "integrity": "sha512-xlAWwPleNRHwF37AhrZurOxA1wyXowW4PqVXZVUNCLjB48CqdPJoJWkrpH2nij9Q3Lb7rtWindtoXwxjxlKKCA==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.20.0.tgz", + "integrity": "sha512-dq70RUw6UK9ei7vxc4KQtBRk7qkHZv447OUZ6RPQMQl71I3NZxQJX/f32Smr+iqWrB02pHKn2yAdHBb0KNrRMA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.11.0", - "@typescript-eslint/types": "7.11.0", - "@typescript-eslint/typescript-estree": "7.11.0" + "@typescript-eslint/scope-manager": "8.20.0", + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/typescript-estree": "8.20.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.11.0.tgz", - "integrity": "sha512-27tGdVEiutD4POirLZX4YzT180vevUURJl4wJGmm6TrQoiYwuxTIY98PBp6L2oN+JQxzE0URvYlzJaBHIekXAw==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.20.0.tgz", + "integrity": "sha512-J7+VkpeGzhOt3FeG1+SzhiMj9NzGD/M6KoGn9f4dbz3YzK9hvbhVTmLj/HiTp9DazIzJ8B4XcM80LrR9Dm1rJw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@typescript-eslint/types": "7.11.0", - "@typescript-eslint/visitor-keys": "7.11.0" + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -10513,14 +10486,13 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.11.0.tgz", - "integrity": "sha512-MPEsDRZTyCiXkD4vd3zywDCifi7tatc4K37KqTprCvaXptP7Xlpdw0NR2hRJTetG5TxbWDB79Ys4kLmHliEo/w==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.20.0.tgz", + "integrity": "sha512-cqaMiY72CkP+2xZRrFt3ExRBu0WmVitN/rYPZErA80mHjHx/Svgp8yfbzkJmDoQ/whcytOPO9/IZXnOc+wigRA==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -10528,54 +10500,63 @@ } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.11.0.tgz", - "integrity": "sha512-cxkhZ2C/iyi3/6U9EPc5y+a6csqHItndvN/CzbNXTNrsC3/ASoYQZEt9uMaEp+xFNjasqQyszp5TumAVKKvJeQ==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.20.0.tgz", + "integrity": "sha512-Y7ncuy78bJqHI35NwzWol8E0X7XkRVS4K4P4TCyzWkOJih5NDvtoRDW4Ba9YJJoB2igm9yXDdYI/+fkiiAxPzA==", "dev": true, - "license": "BSD-2-Clause", - "peer": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.11.0", - "@typescript-eslint/visitor-keys": "7.11.0", + "@typescript-eslint/types": "8.20.0", + "@typescript-eslint/visitor-keys": "8.20.0", "debug": "^4.3.4", - "globby": "^11.1.0", + "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.11.0.tgz", - "integrity": "sha512-7syYk4MzjxTEk0g/w3iqtgxnFQspDJfn6QKD36xMuuhTzjcxY7F8EmBLnALjVyaOF1/bVocu3bS/2/F7rXrveQ==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.20.0.tgz", + "integrity": "sha512-v/BpkeeYAsPkKCkR8BDwcno0llhzWVqPOamQrAEMdpZav2Y9OVjd9dwJyBLJWwf335B5DmlifECIkZRJCaGaHA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@typescript-eslint/types": "7.11.0", - "eslint-visitor-keys": "^3.4.3" + "@typescript-eslint/types": "8.20.0", + "eslint-visitor-keys": "^4.2.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/utils/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@typescript-eslint/visitor-keys": { "version": "8.19.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.19.1.tgz", @@ -16382,161 +16363,21 @@ } }, "node_modules/eslint-plugin-storybook": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-0.8.0.tgz", - "integrity": "sha512-CZeVO5EzmPY7qghO2t64oaFM+8FTaD4uzOEjHKp516exyTKo+skKAL9GI3QALS2BXhyALJjNtwbmr1XinGE8bA==", + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-0.11.2.tgz", + "integrity": "sha512-0Z4DUklJrC+GHjCRXa7PYfPzWC15DaVnwaOYenpgXiCEijXPZkLKCms+rHhtoRcWccP7Z8DpOOaP1gc3P9oOwg==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/csf": "^0.0.1", - "@typescript-eslint/utils": "^5.62.0", - "requireindex": "^1.2.0", + "@storybook/csf": "^0.1.11", + "@typescript-eslint/utils": "^8.8.1", "ts-dedent": "^2.2.0" }, "engines": { "node": ">= 18" }, "peerDependencies": { - "eslint": ">=6" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/@storybook/csf": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.0.1.tgz", - "integrity": "sha512-USTLkZze5gkel8MYCujSRBVIrUQ3YPBrLOx7GNk/0wttvVtlzWXAq9eLbQ4p/NicGxP+3T7KPEMVV//g+yubpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash": "^4.17.15" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/scope-manager": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", - "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/types": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", - "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/typescript-estree": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", - "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/visitor-keys": "5.62.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/utils": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", - "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.62.0", - "@typescript-eslint/types": "5.62.0", - "@typescript-eslint/typescript-estree": "5.62.0", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/visitor-keys": { - "version": "5.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", - "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "5.62.0", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-plugin-storybook/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/eslint-plugin-storybook/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" + "eslint": ">=8" } }, "node_modules/eslint-plugin-tailwindcss": { @@ -30908,17 +30749,16 @@ } }, "node_modules/ts-api-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.0.tgz", - "integrity": "sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz", + "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { - "node": ">=16" + "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=4.2.0" + "typescript": ">=4.8.4" } }, "node_modules/ts-dedent": { diff --git a/package.json b/package.json index 5d7c2ace64d..c4248bcdbab 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,7 @@ "eslint-plugin-import": "2.29.1", "eslint-plugin-rxjs": "5.0.3", "eslint-plugin-rxjs-angular": "2.0.1", - "eslint-plugin-storybook": "0.8.0", + "eslint-plugin-storybook": "0.11.2", "eslint-plugin-tailwindcss": "3.17.5", "html-loader": "5.1.0", "html-webpack-injector": "1.1.4", From 918c68a9f643fdf4e031620431190b21448d973d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 16 Jan 2025 16:08:22 +0100 Subject: [PATCH 12/13] [deps] SM: Update eslint-plugin-import to v2.31.0 (#11066) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Oscar Hinton --- package-lock.json | 35 ++++++++++++++++++++++------------- package.json | 2 +- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index bf724bae691..9f1acd6b679 100644 --- a/package-lock.json +++ b/package-lock.json @@ -143,7 +143,7 @@ "eslint": "8.57.1", "eslint-config-prettier": "9.1.0", "eslint-import-resolver-typescript": "3.6.1", - "eslint-plugin-import": "2.29.1", + "eslint-plugin-import": "2.31.0", "eslint-plugin-rxjs": "5.0.3", "eslint-plugin-rxjs-angular": "2.0.1", "eslint-plugin-storybook": "0.11.2", @@ -7990,6 +7990,13 @@ "darwin" ] }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, "node_modules/@schematics/angular": { "version": "18.2.12", "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-18.2.12.tgz", @@ -16234,35 +16241,37 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", - "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", + "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", "dev": true, "license": "MIT", "dependencies": { - "array-includes": "^3.1.7", - "array.prototype.findlastindex": "^1.2.3", + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", "array.prototype.flat": "^1.3.2", "array.prototype.flatmap": "^1.3.2", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.8.0", - "hasown": "^2.0.0", - "is-core-module": "^2.13.1", + "eslint-module-utils": "^2.12.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", - "object.fromentries": "^2.0.7", - "object.groupby": "^1.0.1", - "object.values": "^1.1.7", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.8", "tsconfig-paths": "^3.15.0" }, "engines": { "node": ">=4" }, "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, "node_modules/eslint-plugin-import/node_modules/brace-expansion": { diff --git a/package.json b/package.json index c4248bcdbab..2cd4d7b4706 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,7 @@ "eslint": "8.57.1", "eslint-config-prettier": "9.1.0", "eslint-import-resolver-typescript": "3.6.1", - "eslint-plugin-import": "2.29.1", + "eslint-plugin-import": "2.31.0", "eslint-plugin-rxjs": "5.0.3", "eslint-plugin-rxjs-angular": "2.0.1", "eslint-plugin-storybook": "0.11.2", From 9a6f00ef119c92c10d6c6488b7ef5bc1db84cf51 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 16 Jan 2025 16:24:46 +0100 Subject: [PATCH 13/13] [deps] SM: Update eslint-import-resolver-typescript to v3.7.0 (#10845) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 175 ++++++++++++++-------------------------------- package.json | 2 +- 2 files changed, 53 insertions(+), 124 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9f1acd6b679..604de469007 100644 --- a/package-lock.json +++ b/package-lock.json @@ -142,7 +142,7 @@ "electron-updater": "6.3.9", "eslint": "8.57.1", "eslint-config-prettier": "9.1.0", - "eslint-import-resolver-typescript": "3.6.1", + "eslint-import-resolver-typescript": "3.7.0", "eslint-plugin-import": "2.31.0", "eslint-plugin-rxjs": "5.0.3", "eslint-plugin-rxjs-angular": "2.0.1", @@ -950,18 +950,6 @@ } } }, - "node_modules/@angular-devkit/build-angular/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@angular-devkit/build-angular/node_modules/webpack": { "version": "5.94.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", @@ -1324,18 +1312,6 @@ "node": ">= 4" } }, - "node_modules/@angular-eslint/schematics/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@angular-eslint/template-parser": { "version": "18.4.3", "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-18.4.3.tgz", @@ -1853,18 +1829,6 @@ "node": ">=14.0.0" } }, - "node_modules/@angular/build/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@angular/build/node_modules/slice-ansi": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", @@ -2060,18 +2024,6 @@ "node": ">=18.0.0" } }, - "node_modules/@angular/cli/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@angular/cli/node_modules/slice-ansi": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", @@ -5042,19 +4994,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@compodoc/compodoc/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@compodoc/compodoc/node_modules/uuid": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", @@ -7499,6 +7438,16 @@ "node": ">= 8" } }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, "node_modules/@npmcli/agent": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", @@ -8864,19 +8813,6 @@ "storybook": "^8.4.7" } }, - "node_modules/@storybook/core/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@storybook/csf": { "version": "0.1.11", "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.1.11.tgz", @@ -15610,19 +15546,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/electron-updater/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/electron/node_modules/@types/node": { "version": "20.17.12", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.12.tgz", @@ -16187,19 +16110,20 @@ } }, "node_modules/eslint-import-resolver-typescript": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.1.tgz", - "integrity": "sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.7.0.tgz", + "integrity": "sha512-Vrwyi8HHxY97K5ebydMtffsWAn1SCR9eol49eCd5fJS4O1WV7PaAjbcjmbfJJSMz/t4Mal212Uz/fQZrOB8mow==", "dev": true, "license": "ISC", "dependencies": { - "debug": "^4.3.4", - "enhanced-resolve": "^5.12.0", - "eslint-module-utils": "^2.7.4", - "fast-glob": "^3.3.1", - "get-tsconfig": "^4.5.0", - "is-core-module": "^2.11.0", - "is-glob": "^4.0.3" + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.3.7", + "enhanced-resolve": "^5.15.0", + "fast-glob": "^3.3.2", + "get-tsconfig": "^4.7.5", + "is-bun-module": "^1.0.2", + "is-glob": "^4.0.3", + "stable-hash": "^0.0.4" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -16209,7 +16133,16 @@ }, "peerDependencies": { "eslint": "*", - "eslint-plugin-import": "*" + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } } }, "node_modules/eslint-module-utils": { @@ -19354,6 +19287,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-bun-module": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-1.3.0.tgz", + "integrity": "sha512-DgXeu5UWI0IsMQundYb5UAOzm6G2eVnarJ0byP6Tm55iZNKceD59LNPA2L4VvsScTtHcw0yEkVwSf7PC+QoLSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.6.3" + } + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -28852,13 +28795,10 @@ } }, "node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -28874,24 +28814,6 @@ "license": "MIT", "optional": true }, - "node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC" - }, "node_modules/send": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/send/-/send-1.1.0.tgz", @@ -29613,6 +29535,13 @@ "dev": true, "license": "ISC" }, + "node_modules/stable-hash": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.4.tgz", + "integrity": "sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==", + "dev": true, + "license": "MIT" + }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", diff --git a/package.json b/package.json index 2cd4d7b4706..2b2abb8d0bf 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "electron-updater": "6.3.9", "eslint": "8.57.1", "eslint-config-prettier": "9.1.0", - "eslint-import-resolver-typescript": "3.6.1", + "eslint-import-resolver-typescript": "3.7.0", "eslint-plugin-import": "2.31.0", "eslint-plugin-rxjs": "5.0.3", "eslint-plugin-rxjs-angular": "2.0.1",