From d6e1fe70cab98ab091322f0316fc6bbcb54c5c65 Mon Sep 17 00:00:00 2001 From: Vijay Oommen Date: Thu, 5 Dec 2024 11:24:51 -0600 Subject: [PATCH 001/112] PM-15091 Remove client side featureflag.AccessIntelligence and use DB feature flag (#12247) * PM-15091 remove featureflag.AccessIntelligence * removed unwanted lines of code * fixed merge conflict --- .../organizations/layouts/organization-layout.component.html | 2 +- .../organizations/layouts/organization-layout.component.ts | 4 ---- .../access-intelligence-routing.module.ts | 5 ++--- .../src/admin-console/models/data/organization.data.spec.ts | 1 + .../src/admin-console/models/data/organization.data.ts | 2 ++ libs/common/src/admin-console/models/domain/organization.ts | 2 ++ .../admin-console/models/response/organization.response.ts | 2 ++ .../models/response/profile-organization.response.ts | 2 ++ libs/common/src/enums/feature-flag.enum.ts | 2 -- 9 files changed, 12 insertions(+), 10 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html index ce832ef06e..fa4d027d0f 100644 --- a/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html +++ b/apps/web/src/app/admin-console/organizations/layouts/organization-layout.component.html @@ -4,7 +4,7 @@ p.organizationId), switchMap((id) => this.organizationService.organizations$.pipe(getById(id))), diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/access-intelligence-routing.module.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/access-intelligence-routing.module.ts index c13cc0efae..993d9c0a13 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/access-intelligence-routing.module.ts +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/access-intelligence-routing.module.ts @@ -1,15 +1,14 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; -import { canAccessFeature } from "@bitwarden/angular/platform/guard/feature-flag.guard"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { organizationPermissionsGuard } from "@bitwarden/web-vault/app/admin-console/organizations/guards/org-permissions.guard"; import { RiskInsightsComponent } from "./risk-insights.component"; const routes: Routes = [ { path: "risk-insights", - canActivate: [canAccessFeature(FeatureFlag.AccessIntelligence)], + canActivate: [organizationPermissionsGuard((org) => org.useRiskInsights)], component: RiskInsightsComponent, data: { titleId: "RiskInsights", diff --git a/libs/common/src/admin-console/models/data/organization.data.spec.ts b/libs/common/src/admin-console/models/data/organization.data.spec.ts index 12c5339a24..da9a82e7c5 100644 --- a/libs/common/src/admin-console/models/data/organization.data.spec.ts +++ b/libs/common/src/admin-console/models/data/organization.data.spec.ts @@ -56,6 +56,7 @@ describe("ORGANIZATIONS state", () => { allowAdminAccessToAllCollectionItems: false, familySponsorshipLastSyncDate: new Date(), userIsManagedByOrganization: false, + useRiskInsights: false, }, }; const result = sut.deserializer(JSON.parse(JSON.stringify(expectedResult))); diff --git a/libs/common/src/admin-console/models/data/organization.data.ts b/libs/common/src/admin-console/models/data/organization.data.ts index d8032fd6fc..23a0f360f9 100644 --- a/libs/common/src/admin-console/models/data/organization.data.ts +++ b/libs/common/src/admin-console/models/data/organization.data.ts @@ -56,6 +56,7 @@ export class OrganizationData { limitCollectionDeletion: boolean; allowAdminAccessToAllCollectionItems: boolean; userIsManagedByOrganization: boolean; + useRiskInsights: boolean; constructor( response?: ProfileOrganizationResponse, @@ -116,6 +117,7 @@ export class OrganizationData { this.limitCollectionDeletion = response.limitCollectionDeletion; this.allowAdminAccessToAllCollectionItems = response.allowAdminAccessToAllCollectionItems; this.userIsManagedByOrganization = response.userIsManagedByOrganization; + this.useRiskInsights = response.useRiskInsights; this.isMember = options.isMember; this.isProviderUser = options.isProviderUser; diff --git a/libs/common/src/admin-console/models/domain/organization.ts b/libs/common/src/admin-console/models/domain/organization.ts index 497d3b0889..3f3ec0f445 100644 --- a/libs/common/src/admin-console/models/domain/organization.ts +++ b/libs/common/src/admin-console/models/domain/organization.ts @@ -81,6 +81,7 @@ export class Organization { * matches one of the verified domains of that organization, and the user is a member of it. */ userIsManagedByOrganization: boolean; + useRiskInsights: boolean; constructor(obj?: OrganizationData) { if (obj == null) { @@ -137,6 +138,7 @@ export class Organization { this.limitCollectionDeletion = obj.limitCollectionDeletion; this.allowAdminAccessToAllCollectionItems = obj.allowAdminAccessToAllCollectionItems; this.userIsManagedByOrganization = obj.userIsManagedByOrganization; + this.useRiskInsights = obj.useRiskInsights; } get canAccess() { diff --git a/libs/common/src/admin-console/models/response/organization.response.ts b/libs/common/src/admin-console/models/response/organization.response.ts index e033646797..fd460896a3 100644 --- a/libs/common/src/admin-console/models/response/organization.response.ts +++ b/libs/common/src/admin-console/models/response/organization.response.ts @@ -35,6 +35,7 @@ export class OrganizationResponse extends BaseResponse { limitCollectionCreation: boolean; limitCollectionDeletion: boolean; allowAdminAccessToAllCollectionItems: boolean; + useRiskInsights: boolean; constructor(response: any) { super(response); @@ -75,5 +76,6 @@ export class OrganizationResponse extends BaseResponse { this.allowAdminAccessToAllCollectionItems = this.getResponseProperty( "AllowAdminAccessToAllCollectionItems", ); + this.useRiskInsights = this.getResponseProperty("UseRiskInsights"); } } diff --git a/libs/common/src/admin-console/models/response/profile-organization.response.ts b/libs/common/src/admin-console/models/response/profile-organization.response.ts index ed1f45d491..9c4b8885ab 100644 --- a/libs/common/src/admin-console/models/response/profile-organization.response.ts +++ b/libs/common/src/admin-console/models/response/profile-organization.response.ts @@ -53,6 +53,7 @@ export class ProfileOrganizationResponse extends BaseResponse { limitCollectionDeletion: boolean; allowAdminAccessToAllCollectionItems: boolean; userIsManagedByOrganization: boolean; + useRiskInsights: boolean; constructor(response: any) { super(response); @@ -117,5 +118,6 @@ export class ProfileOrganizationResponse extends BaseResponse { "AllowAdminAccessToAllCollectionItems", ); this.userIsManagedByOrganization = this.getResponseProperty("UserIsManagedByOrganization"); + this.useRiskInsights = this.getResponseProperty("UseRiskInsights"); } } diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 96283b0000..66d6b155e9 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -31,7 +31,6 @@ export enum FeatureFlag { CipherKeyEncryption = "cipher-key-encryption", VerifiedSsoDomainEndpoint = "pm-12337-refactor-sso-details-endpoint", PM11901_RefactorSelfHostingLicenseUploader = "PM-11901-refactor-self-hosting-license-uploader", - AccessIntelligence = "pm-13227-access-intelligence", PM14505AdminConsoleIntegrationPage = "pm-14505-admin-console-integration-page", CriticalApps = "pm-14466-risk-insights-critical-application", TrialPaymentOptional = "PM-8163-trial-payment", @@ -81,7 +80,6 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.CipherKeyEncryption]: FALSE, [FeatureFlag.VerifiedSsoDomainEndpoint]: FALSE, [FeatureFlag.PM11901_RefactorSelfHostingLicenseUploader]: FALSE, - [FeatureFlag.AccessIntelligence]: FALSE, [FeatureFlag.PM14505AdminConsoleIntegrationPage]: FALSE, [FeatureFlag.CriticalApps]: FALSE, [FeatureFlag.TrialPaymentOptional]: FALSE, From 8d68a2dd58d7cc79c2c5440ce99b7d593d4e9393 Mon Sep 17 00:00:00 2001 From: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Date: Thu, 5 Dec 2024 20:22:13 -0500 Subject: [PATCH 002/112] Auth/PM-13659 - 2FA Timeout - Attempted Fix (#12263) fix(auth): attempt to resolve 2FA session timeout issue --- .../src/service-container/service-container.ts | 7 +++++++ libs/angular/src/services/injection-tokens.ts | 6 +++++- .../src/services/jslib-services.module.ts | 9 ++++++++- .../login-strategies/login-strategy.service.ts | 17 +++++++++++------ 4 files changed, 31 insertions(+), 8 deletions(-) diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index 21e8f9f208..ce944db78f 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -17,6 +17,7 @@ import { PinService, PinServiceAbstraction, UserDecryptionOptionsService, + Executor, } from "@bitwarden/auth/common"; import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service"; import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service"; @@ -614,6 +615,11 @@ export class ServiceContainer { this.configService, ); + // Execute any authn session timeout logic without any wrapping logic. + // An executor is required to ensure the logic is executed in an Angular context when it + // it is available. + const authnSessionTimeoutExecutor: Executor = (fn) => fn(); + this.loginStrategyService = new LoginStrategyService( this.accountService, this.masterPasswordService, @@ -640,6 +646,7 @@ export class ServiceContainer { this.vaultTimeoutSettingsService, this.kdfConfigService, this.taskSchedulerService, + authnSessionTimeoutExecutor, ); // FIXME: CLI does not support autofill diff --git a/libs/angular/src/services/injection-tokens.ts b/libs/angular/src/services/injection-tokens.ts index 86c5642a0c..69a1ed3f61 100644 --- a/libs/angular/src/services/injection-tokens.ts +++ b/libs/angular/src/services/injection-tokens.ts @@ -1,7 +1,7 @@ import { InjectionToken } from "@angular/core"; import { Observable, Subject } from "rxjs"; -import { LogoutReason } from "@bitwarden/auth/common"; +import { Executor, LogoutReason } from "@bitwarden/auth/common"; import { ClientType } from "@bitwarden/common/enums"; import { RegionConfig } from "@bitwarden/common/platform/abstractions/environment.service"; import { @@ -68,3 +68,7 @@ export const REFRESH_ACCESS_TOKEN_ERROR_CALLBACK = new SafeInjectionToken<() => export const ENV_ADDITIONAL_REGIONS = new SafeInjectionToken( "ENV_ADDITIONAL_REGIONS", ); + +export const AUTHN_SESSION_TIMEOUT_EXECUTOR = new SafeInjectionToken( + "AuthnSessionTimeoutExecutor", +); diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index a43f1fa07a..8637e26a2b 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -1,4 +1,4 @@ -import { ErrorHandler, LOCALE_ID, NgModule } from "@angular/core"; +import { ErrorHandler, LOCALE_ID, NgModule, NgZone } from "@angular/core"; import { Subject } from "rxjs"; import { @@ -319,6 +319,7 @@ import { CLIENT_TYPE, REFRESH_ACCESS_TOKEN_ERROR_CALLBACK, ENV_ADDITIONAL_REGIONS, + AUTHN_SESSION_TIMEOUT_EXECUTOR, } from "./injection-tokens"; import { ModalService } from "./modal.service"; @@ -411,6 +412,11 @@ const safeProviders: SafeProvider[] = [ TokenServiceAbstraction, ], }), + safeProvider({ + provide: AUTHN_SESSION_TIMEOUT_EXECUTOR, + useFactory: (ngZone: NgZone) => (fn: () => void) => ngZone.run(fn), + deps: [NgZone], + }), safeProvider({ provide: LoginStrategyServiceAbstraction, useClass: LoginStrategyService, @@ -440,6 +446,7 @@ const safeProviders: SafeProvider[] = [ VaultTimeoutSettingsServiceAbstraction, KdfConfigService, TaskSchedulerService, + AUTHN_SESSION_TIMEOUT_EXECUTOR, ], }), safeProvider({ diff --git a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts index 99e3c057e1..1d5001f1f0 100644 --- a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts +++ b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts @@ -71,6 +71,8 @@ import { const sessionTimeoutLength = 5 * 60 * 1000; // 5 minutes +export type Executor = (fn: () => void) => void; + export class LoginStrategyService implements LoginStrategyServiceAbstraction { private sessionTimeoutSubscription: Subscription; private currentAuthnTypeState: GlobalState; @@ -118,6 +120,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { protected vaultTimeoutSettingsService: VaultTimeoutSettingsService, protected kdfConfigService: KdfConfigService, protected taskSchedulerService: TaskSchedulerService, + private authnSessionTimeoutExecutor: Executor = (fn) => fn(), // Default to no-op ) { this.currentAuthnTypeState = this.stateProvider.get(CURRENT_LOGIN_STRATEGY_KEY); this.loginStrategyCacheState = this.stateProvider.get(CACHE_KEY); @@ -128,12 +131,14 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { this.taskSchedulerService.registerTaskHandler( ScheduledTaskNames.loginStrategySessionTimeout, async () => { - this.twoFactorTimeoutSubject.next(true); - try { - await this.clearCache(); - } catch (e) { - this.logService.error("Failed to clear cache during session timeout", e); - } + this.authnSessionTimeoutExecutor(async () => { + this.twoFactorTimeoutSubject.next(true); + try { + await this.clearCache(); + } catch (e) { + this.logService.error("Failed to clear cache during session timeout", e); + } + }); }, ); From f95cc7b82cb0e8f1f5921740c4033e2ddbcbd536 Mon Sep 17 00:00:00 2001 From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Date: Fri, 6 Dec 2024 15:17:48 +0100 Subject: [PATCH 003/112] Resolve the Unauthorized issue (#12262) --- apps/web/src/app/vault/individual-vault/vault.component.ts | 5 ++++- apps/web/src/app/vault/org-vault/vault.component.ts | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/web/src/app/vault/individual-vault/vault.component.ts b/apps/web/src/app/vault/individual-vault/vault.component.ts index 5f66bb49af..73a5de2ad5 100644 --- a/apps/web/src/app/vault/individual-vault/vault.component.ts +++ b/apps/web/src/app/vault/individual-vault/vault.component.ts @@ -210,7 +210,10 @@ export class VaultComponent implements OnInit, OnDestroy { protected organizationsPaymentStatus$: Observable = combineLatest([ this.organizationService.organizations$.pipe( - map((organizations) => organizations?.filter((org) => org.isOwner) ?? []), + map( + (organizations) => + organizations?.filter((org) => org.isOwner && org.canViewBillingHistory) ?? [], + ), ), this.hasSubscription$, ]).pipe( diff --git a/apps/web/src/app/vault/org-vault/vault.component.ts b/apps/web/src/app/vault/org-vault/vault.component.ts index 18cc6e49ab..eb7d586cf7 100644 --- a/apps/web/src/app/vault/org-vault/vault.component.ts +++ b/apps/web/src/app/vault/org-vault/vault.component.ts @@ -592,7 +592,9 @@ export class VaultComponent implements OnInit, OnDestroy { organization$, this.hasSubscription$.pipe(filter((hasSubscription) => hasSubscription !== null)), ]).pipe( - filter(([org, hasSubscription]) => org.isOwner && hasSubscription), + filter( + ([org, hasSubscription]) => org.isOwner && hasSubscription && org.canViewBillingHistory, + ), switchMap(([org]) => combineLatest([ of(org), From f16bfa4cd2136d55d1cfd4d4f4e63b8d364bea2a Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Fri, 6 Dec 2024 16:31:30 +0100 Subject: [PATCH 004/112] [PM-9035] desktop build logic to provide credentials to os on sync (#10181) * feat: scaffold desktop_objc * feat: rename fido2 to autofill * feat: scaffold electron autofill * feat: auto call hello world on init * feat: scaffold call to basic objc function * feat: simple log that checks if autofill is enabled * feat: adding some availability guards * feat: scaffold services and allow calls from inspector * feat: create custom type for returning strings across rust/objc boundary * chore: clean up comments * feat: enable ARC * feat: add util function `c_string_to_nsstring` * chore: refactor and rename to `run_command` * feat: add try-catch around command execution * feat: properly implement command calling Add static typing. Add proper error handling. * feat: add autoreleasepool to avoid memory leaks * chore: change objc names to camelCase * fix: error returning * feat: extract some helper functions into utils class * feat: scaffold status command * feat: implement status command * feat: implement password credential mapping * wip: implement sync command This crashes because we are not properly handling the fact that `saveCredentialIdentities` uses callbacks, resulting in a race condition where we try to access a variable (result) that has already gotten dealloc'd. * feat: first version of callback * feat: make run_command async * feat: functioning callback returns * chore: refactor to make objc code easier to read and use * feat: refactor everything to use new callback return method * feat: re-implement status command with callback * fix: warning about CommandContext not being FFI-safe * feat: implement sync command using callbacks * feat: implement manual password credential sync * feat: add auto syncing * docs: add todo * feat: add support for passkeys * chore: move desktop autofill service to init service * feat: auto-add all .m files to builder * fix: native build on unix and windows * fix: unused compiler warnings * fix: napi type exports * feat: add corresponding dist command * feat: comment signing profile until we fix signing * fix: build breaking on non-macOS platforms * chore: cargo lock update * chore: revert accidental version change * feat: put sync behind feature flag * chore: put files in autofill folder * fix: obj-c code not recompiling on changes * feat: add `namespace` to commands * fix: linting complaining about flag * feat: add autofill as owner of their objc code * chore: make autofill owner of run_command in core crate * fix: re-add napi annotation * fix: remove dev bypass --- .github/CODEOWNERS | 2 + apps/desktop/desktop_native/Cargo.lock | 245 ++++++++++++------ apps/desktop/desktop_native/core/Cargo.toml | 11 +- .../desktop_native/core/src/autofill/macos.rs | 5 + .../desktop_native/core/src/autofill/mod.rs | 5 + .../desktop_native/core/src/autofill/unix.rs | 5 + .../core/src/autofill/windows.rs | 5 + apps/desktop/desktop_native/core/src/lib.rs | 6 +- apps/desktop/desktop_native/napi/index.d.ts | 3 + apps/desktop/desktop_native/napi/src/lib.rs | 57 +++- apps/desktop/desktop_native/objc/Cargo.toml | 21 ++ apps/desktop/desktop_native/objc/build.rs | 22 ++ apps/desktop/desktop_native/objc/src/lib.rs | 124 +++++++++ .../desktop_native/objc/src/native/.clangd | 2 + .../src/native/autofill/commands/status.h | 8 + .../src/native/autofill/commands/status.m | 57 ++++ .../objc/src/native/autofill/commands/sync.h | 8 + .../objc/src/native/autofill/commands/sync.m | 59 +++++ .../native/autofill/run_autofill_command.h | 8 + .../native/autofill/run_autofill_command.m | 20 ++ .../desktop_native/objc/src/native/interop.h | 47 ++++ .../desktop_native/objc/src/native/interop.m | 71 +++++ .../objc/src/native/run_command.m | 39 +++ .../desktop_native/objc/src/native/utils.h | 11 + .../desktop_native/objc/src/native/utils.m | 28 ++ .../CredentialProviderViewController.swift | 21 +- apps/desktop/package.json | 1 + apps/desktop/scripts/build-macos-extension.js | 5 +- apps/desktop/src/app/services/init.service.ts | 4 + .../src/app/services/services.module.ts | 6 + apps/desktop/src/autofill/preload.ts | 9 + .../services/desktop-autofill.service.ts | 121 +++++++++ apps/desktop/src/main.ts | 5 + .../src/platform/main/autofill/command.ts | 23 ++ .../main/autofill/native-autofill.main.ts | 53 ++++ .../platform/main/autofill/status.command.ts | 20 ++ .../platform/main/autofill/sync.command.ts | 37 +++ apps/desktop/src/preload.ts | 2 + libs/common/src/enums/feature-flag.enum.ts | 2 + .../services/fido2/fido2-autofill-utils.ts | 26 ++ .../view/fido2-credential-autofill.view.ts | 7 + 41 files changed, 1099 insertions(+), 112 deletions(-) create mode 100644 apps/desktop/desktop_native/core/src/autofill/macos.rs create mode 100644 apps/desktop/desktop_native/core/src/autofill/mod.rs create mode 100644 apps/desktop/desktop_native/core/src/autofill/unix.rs create mode 100644 apps/desktop/desktop_native/core/src/autofill/windows.rs create mode 100644 apps/desktop/desktop_native/objc/Cargo.toml create mode 100644 apps/desktop/desktop_native/objc/build.rs create mode 100644 apps/desktop/desktop_native/objc/src/lib.rs create mode 100644 apps/desktop/desktop_native/objc/src/native/.clangd create mode 100644 apps/desktop/desktop_native/objc/src/native/autofill/commands/status.h create mode 100644 apps/desktop/desktop_native/objc/src/native/autofill/commands/status.m create mode 100644 apps/desktop/desktop_native/objc/src/native/autofill/commands/sync.h create mode 100644 apps/desktop/desktop_native/objc/src/native/autofill/commands/sync.m create mode 100644 apps/desktop/desktop_native/objc/src/native/autofill/run_autofill_command.h create mode 100644 apps/desktop/desktop_native/objc/src/native/autofill/run_autofill_command.m create mode 100644 apps/desktop/desktop_native/objc/src/native/interop.h create mode 100644 apps/desktop/desktop_native/objc/src/native/interop.m create mode 100644 apps/desktop/desktop_native/objc/src/native/run_command.m create mode 100644 apps/desktop/desktop_native/objc/src/native/utils.h create mode 100644 apps/desktop/desktop_native/objc/src/native/utils.m create mode 100644 apps/desktop/src/autofill/preload.ts create mode 100644 apps/desktop/src/autofill/services/desktop-autofill.service.ts create mode 100644 apps/desktop/src/platform/main/autofill/command.ts create mode 100644 apps/desktop/src/platform/main/autofill/native-autofill.main.ts create mode 100644 apps/desktop/src/platform/main/autofill/status.command.ts create mode 100644 apps/desktop/src/platform/main/autofill/sync.command.ts create mode 100644 libs/common/src/platform/services/fido2/fido2-autofill-utils.ts create mode 100644 libs/common/src/vault/models/view/fido2-credential-autofill.view.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 12a8c18233..959c176c5c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -103,6 +103,8 @@ apps/web/src/app/layouts @bitwarden/team-design-system ## Desktop native module ## apps/desktop/desktop_native @bitwarden/team-platform-dev +apps/desktop/desktop_native/objc/src/native/autofill @bitwarden/team-autofill-dev +apps/desktop/desktop_native/core/src/autofill @bitwarden/team-autofill-dev ## Key management team files ## apps/desktop/src/key-management @bitwarden/team-key-management-dev diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 82dbebb12d..148e56fcb0 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -62,6 +62,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + [[package]] name = "anyhow" version = "1.0.93" @@ -147,9 +153,9 @@ dependencies = [ [[package]] name = "async-io" -version = "2.3.4" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" +checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" dependencies = [ "async-lock", "cfg-if", @@ -412,9 +418,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "cbc" @@ -427,9 +433,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.34" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b9470d453346108f93a59222a9a1a5724db32d0a4727b7ab7ace4b4d822dc9" +checksum = "f34d93e62b03caf570cccc334cbc6c2fceca82f39211051345108adcba3eebdc" dependencies = [ "shlex", ] @@ -474,6 +480,32 @@ dependencies = [ "zeroize", ] +[[package]] +name = "clap" +version = "4.5.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69371e34337c4c984bbe322360c2547210bf632eb2814bbe78a6e87a2935bd2b" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e24c1b4099818523236a8ca881d2b45db98dadfb4625cf6608c12069fcbbde1" +dependencies = [ + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_lex" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" + [[package]] name = "clipboard-win" version = "5.4.0" @@ -517,6 +549,16 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.0" @@ -535,9 +577,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -561,9 +603,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", "syn", @@ -606,25 +648,26 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.129" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbdc8cca144dce1c4981b5c9ab748761619979e515c3d53b5df385c677d1d007" +checksum = "05e1ec88093d2abd9cf1b09ffd979136b8e922bf31cad966a8fe0d73233112ef" dependencies = [ "cc", + "cxxbridge-cmd", "cxxbridge-flags", "cxxbridge-macro", + "foldhash", "link-cplusplus", ] [[package]] name = "cxx-build" -version = "1.0.129" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5764c3142ab44fcf857101d12c0ddf09c34499900557c764f5ad0597159d1fc" +checksum = "9afa390d956ee7ccb41aeed7ed7856ab3ffb4fc587e7216be7e0f83e949b4e6c" dependencies = [ "cc", "codespan-reporting", - "once_cell", "proc-macro2", "quote", "scratch", @@ -632,19 +675,33 @@ dependencies = [ ] [[package]] -name = "cxxbridge-flags" -version = "1.0.129" +name = "cxxbridge-cmd" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d422aff542b4fa28c2ce8e5cc202d42dbf24702345c1fba3087b2d3f8a1b90ff" +checksum = "3c23bfff654d6227cbc83de8e059d2f8678ede5fc3a6c5a35d5c379983cc61e6" +dependencies = [ + "clap", + "codespan-reporting", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.133" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c01b36e22051bc6928a78583f1621abaaf7621561c2ada1b00f7878fbe2caa" [[package]] name = "cxxbridge-macro" -version = "1.0.129" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1719100f31492cd6adeeab9a0f46cdbc846e615fdb66d7b398aa46ec7fdd06f" +checksum = "f6e14013136fac689345d17b9a6df55977251f11d333c0a571e8d963b55e1f95" dependencies = [ "proc-macro2", "quote", + "rustversion", "syn", ] @@ -692,7 +749,8 @@ dependencies = [ "bitwarden-russh", "byteorder", "cbc", - "core-foundation", + "core-foundation 0.10.0", + "desktop_objc", "dirs", "ed25519", "futures", @@ -743,6 +801,18 @@ dependencies = [ "windows-registry", ] +[[package]] +name = "desktop_objc" +version = "0.0.0" +dependencies = [ + "anyhow", + "cc", + "core-foundation 0.9.4", + "glob", + "thiserror", + "tokio", +] + [[package]] name = "desktop_proxy" version = "0.0.0" @@ -874,12 +944,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -901,9 +971,9 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" dependencies = [ "event-listener", "pin-project-lite", @@ -911,9 +981,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" [[package]] name = "fiat-crypto" @@ -933,6 +1003,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" + [[package]] name = "futures" version = "0.3.31" @@ -983,9 +1059,9 @@ checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f1fa2f9765705486b33fd2acf1577f8ec449c2ba1f318ae5447697b7c08d210" +checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1" dependencies = [ "fastrand", "futures-core", @@ -1083,16 +1159,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] -name = "hashbrown" -version = "0.15.1" +name = "glob" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] -name = "hermit-abi" -version = "0.3.9" +name = "hashbrown" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "hermit-abi" @@ -1138,9 +1214,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", "hashbrown", @@ -1173,9 +1249,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "keytar" @@ -1215,9 +1291,9 @@ checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" [[package]] name = "libloading" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", "windows-targets 0.52.6", @@ -1312,11 +1388,10 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "hermit-abi 0.3.9", "libc", "wasi", "windows-sys 0.52.0", @@ -1859,13 +1934,13 @@ checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "polling" -version = "3.7.3" +version = "3.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" +checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi 0.4.0", + "hermit-abi", "pin-project-lite", "rustix", "tracing", @@ -1921,9 +1996,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.89" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -2016,9 +2091,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -2079,18 +2154,18 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustix" -version = "0.38.37" +version = "0.38.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" dependencies = [ "bitflags", "errno", @@ -2099,6 +2174,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustversion" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" + [[package]] name = "salsa20" version = "0.10.2" @@ -2144,7 +2225,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d0283c0a4a22a0f1b0e4edca251aa20b92fc96eaa09b84bec052f9415e9d71" dependencies = [ "bitflags", - "core-foundation", + "core-foundation 0.10.0", "core-foundation-sys", "libc", "security-framework-sys", @@ -2168,18 +2249,18 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", @@ -2272,9 +2353,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", @@ -2349,6 +2430,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.6.1" @@ -2357,9 +2444,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.87" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -2368,9 +2455,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.13.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" dependencies = [ "cfg-if", "fastrand", @@ -2410,9 +2497,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", @@ -2433,9 +2520,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", @@ -2511,9 +2598,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -2522,9 +2609,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", @@ -2533,9 +2620,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", ] @@ -2572,9 +2659,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-segmentation" diff --git a/apps/desktop/desktop_native/core/Cargo.toml b/apps/desktop/desktop_native/core/Cargo.toml index 6e17cde2fa..4ace1c13e0 100644 --- a/apps/desktop/desktop_native/core/Cargo.toml +++ b/apps/desktop/desktop_native/core/Cargo.toml @@ -23,7 +23,7 @@ sys = [ aes = "=0.8.4" anyhow = "=1.0.93" arboard = { version = "=3.4.1", default-features = false, features = [ - "wayland-data-control", + "wayland-data-control", ] } argon2 = { version = "=0.5.3", features = ["zeroize"] } async-stream = "=0.3.6" @@ -44,10 +44,10 @@ scopeguard = "=1.2.0" sha2 = "=0.10.8" ssh-encoding = "=0.2.0" ssh-key = { version = "=0.6.7", default-features = false, features = [ - "encryption", - "ed25519", - "rsa", - "getrandom", + "encryption", + "ed25519", + "rsa", + "getrandom", ] } bitwarden-russh = { git = "https://github.com/bitwarden/bitwarden-russh.git", rev = "b4e7f2fedbe3df8c35545feb000176d3e7b2bc32" } tokio = { version = "=1.41.1", features = ["io-util", "sync", "macros", "net"] } @@ -81,6 +81,7 @@ keytar = "=0.1.6" core-foundation = { version = "=0.10.0", optional = true } security-framework = { version = "=3.0.0", optional = true } security-framework-sys = { version = "=2.12.0", optional = true } +desktop_objc = { path = "../objc" } [target.'cfg(target_os = "linux")'.dependencies] oo7 = "=0.3.3" diff --git a/apps/desktop/desktop_native/core/src/autofill/macos.rs b/apps/desktop/desktop_native/core/src/autofill/macos.rs new file mode 100644 index 0000000000..08f333abe9 --- /dev/null +++ b/apps/desktop/desktop_native/core/src/autofill/macos.rs @@ -0,0 +1,5 @@ +use anyhow::Result; + +pub async fn run_command(value: String) -> Result { + desktop_objc::run_command(value).await +} diff --git a/apps/desktop/desktop_native/core/src/autofill/mod.rs b/apps/desktop/desktop_native/core/src/autofill/mod.rs new file mode 100644 index 0000000000..5997add240 --- /dev/null +++ b/apps/desktop/desktop_native/core/src/autofill/mod.rs @@ -0,0 +1,5 @@ +#[cfg_attr(target_os = "linux", path = "unix.rs")] +#[cfg_attr(target_os = "windows", path = "windows.rs")] +#[cfg_attr(target_os = "macos", path = "macos.rs")] +mod autofill; +pub use autofill::*; diff --git a/apps/desktop/desktop_native/core/src/autofill/unix.rs b/apps/desktop/desktop_native/core/src/autofill/unix.rs new file mode 100644 index 0000000000..d77130176a --- /dev/null +++ b/apps/desktop/desktop_native/core/src/autofill/unix.rs @@ -0,0 +1,5 @@ +use anyhow::Result; + +pub async fn run_command(value: String) -> Result { + todo!("Unix does not support autofill"); +} diff --git a/apps/desktop/desktop_native/core/src/autofill/windows.rs b/apps/desktop/desktop_native/core/src/autofill/windows.rs new file mode 100644 index 0000000000..2e442263c1 --- /dev/null +++ b/apps/desktop/desktop_native/core/src/autofill/windows.rs @@ -0,0 +1,5 @@ +use anyhow::Result; + +pub async fn run_command(value: String) -> Result { + todo!("Windows does not support autofill"); +} diff --git a/apps/desktop/desktop_native/core/src/lib.rs b/apps/desktop/desktop_native/core/src/lib.rs index f38e6ef97b..b63c773209 100644 --- a/apps/desktop/desktop_native/core/src/lib.rs +++ b/apps/desktop/desktop_native/core/src/lib.rs @@ -1,3 +1,4 @@ +pub mod autofill; #[cfg(feature = "sys")] pub mod biometric; #[cfg(feature = "sys")] @@ -8,9 +9,8 @@ pub mod ipc; #[cfg(feature = "sys")] pub mod password; #[cfg(feature = "sys")] -pub mod process_isolation; -#[cfg(feature = "sys")] pub mod powermonitor; #[cfg(feature = "sys")] - +pub mod process_isolation; +#[cfg(feature = "sys")] pub mod ssh_agent; diff --git a/apps/desktop/desktop_native/napi/index.d.ts b/apps/desktop/desktop_native/napi/index.d.ts index 9ceb30c4ff..0eaba19791 100644 --- a/apps/desktop/desktop_native/napi/index.d.ts +++ b/apps/desktop/desktop_native/napi/index.d.ts @@ -122,6 +122,9 @@ export declare namespace ipc { send(message: string): number } } +export declare namespace autofill { + export function runCommand(value: string): Promise +} export declare namespace crypto { export function argon2(secret: Buffer, salt: Buffer, iterations: number, memory: number, parallelism: number): Promise } diff --git a/apps/desktop/desktop_native/napi/src/lib.rs b/apps/desktop/desktop_native/napi/src/lib.rs index f160b19ad5..5037108afd 100644 --- a/apps/desktop/desktop_native/napi/src/lib.rs +++ b/apps/desktop/desktop_native/napi/src/lib.rs @@ -8,7 +8,8 @@ pub mod passwords { /// Fetch the stored password from the keychain. #[napi] pub async fn get_password(service: String, account: String) -> napi::Result { - desktop_core::password::get_password(&service, &account).await + desktop_core::password::get_password(&service, &account) + .await .map_err(|e| napi::Error::from_reason(e.to_string())) } @@ -19,21 +20,25 @@ pub mod passwords { account: String, password: String, ) -> napi::Result<()> { - desktop_core::password::set_password(&service, &account, &password).await + desktop_core::password::set_password(&service, &account, &password) + .await .map_err(|e| napi::Error::from_reason(e.to_string())) } /// Delete the stored password from the keychain. #[napi] pub async fn delete_password(service: String, account: String) -> napi::Result<()> { - desktop_core::password::delete_password(&service, &account).await + desktop_core::password::delete_password(&service, &account) + .await .map_err(|e| napi::Error::from_reason(e.to_string())) } // Checks if the os secure storage is available #[napi] pub async fn is_available() -> napi::Result { - desktop_core::password::is_available().await.map_err(|e| napi::Error::from_reason(e.to_string())) + desktop_core::password::is_available() + .await + .map_err(|e| napi::Error::from_reason(e.to_string())) } } @@ -244,13 +249,17 @@ pub mod sshagent { pub async fn serve( callback: ThreadsafeFunction<(String, bool), CalleeHandled>, ) -> napi::Result { - let (auth_request_tx, mut auth_request_rx) = tokio::sync::mpsc::channel::<(u32, (String, bool))>(32); - let (auth_response_tx, auth_response_rx) = tokio::sync::broadcast::channel::<(u32, bool)>(32); + let (auth_request_tx, mut auth_request_rx) = + tokio::sync::mpsc::channel::<(u32, (String, bool))>(32); + let (auth_response_tx, auth_response_rx) = + tokio::sync::broadcast::channel::<(u32, bool)>(32); let auth_response_tx_arc = Arc::new(Mutex::new(auth_response_tx)); tokio::spawn(async move { let _ = auth_response_rx; - while let Some((request_id, (cipher_uuid, is_list_request))) = auth_request_rx.recv().await { + while let Some((request_id, (cipher_uuid, is_list_request))) = + auth_request_rx.recv().await + { let cloned_request_id = request_id.clone(); let cloned_cipher_uuid = cipher_uuid.clone(); let cloned_response_tx_arc = auth_response_tx_arc.clone(); @@ -260,23 +269,33 @@ pub mod sshagent { let cipher_uuid = cloned_cipher_uuid; let auth_response_tx_arc = cloned_response_tx_arc; let callback = cloned_callback; - let promise_result: Result, napi::Error> = - callback.call_async(Ok((cipher_uuid, is_list_request))).await; + let promise_result: Result, napi::Error> = callback + .call_async(Ok((cipher_uuid, is_list_request))) + .await; match promise_result { Ok(promise_result) => match promise_result.await { Ok(result) => { - let _ = auth_response_tx_arc.lock().await.send((request_id, result)) + let _ = auth_response_tx_arc + .lock() + .await + .send((request_id, result)) .expect("should be able to send auth response to agent"); } Err(e) => { println!("[SSH Agent Native Module] calling UI callback promise was rejected: {}", e); - let _ = auth_response_tx_arc.lock().await.send((request_id, false)) + let _ = auth_response_tx_arc + .lock() + .await + .send((request_id, false)) .expect("should be able to send auth response to agent"); } }, Err(e) => { println!("[SSH Agent Native Module] calling UI callback could not create promise: {}", e); - let _ = auth_response_tx_arc.lock().await.send((request_id, false)) + let _ = auth_response_tx_arc + .lock() + .await + .send((request_id, false)) .expect("should be able to send auth response to agent"); } } @@ -343,7 +362,9 @@ pub mod sshagent { #[napi] pub fn clear_keys(agent_state: &mut SshAgentState) -> napi::Result<()> { let bitwarden_agent_state = &mut agent_state.state; - bitwarden_agent_state.clear_keys().map_err(|e| napi::Error::from_reason(e.to_string())) + bitwarden_agent_state + .clear_keys() + .map_err(|e| napi::Error::from_reason(e.to_string())) } #[napi] @@ -524,6 +545,16 @@ pub mod ipc { } } +#[napi] +pub mod autofill { + #[napi] + pub async fn run_command(value: String) -> napi::Result { + desktop_core::autofill::run_command(value) + .await + .map_err(|e| napi::Error::from_reason(e.to_string())) + } +} + #[napi] pub mod crypto { use napi::bindgen_prelude::Buffer; diff --git a/apps/desktop/desktop_native/objc/Cargo.toml b/apps/desktop/desktop_native/objc/Cargo.toml new file mode 100644 index 0000000000..4b883ce6fa --- /dev/null +++ b/apps/desktop/desktop_native/objc/Cargo.toml @@ -0,0 +1,21 @@ +[package] +edition = "2021" +license = "GPL-3.0" +name = "desktop_objc" +version = "0.0.0" +publish = false + +[features] +default = [] + +[dependencies] +anyhow = "=1.0.93" +thiserror = "=1.0.69" +tokio = "1.39.1" + +[target.'cfg(target_os = "macos")'.dependencies] +core-foundation = "=0.9.4" + +[build-dependencies] +cc = "1.0.104" +glob = "0.3.1" diff --git a/apps/desktop/desktop_native/objc/build.rs b/apps/desktop/desktop_native/objc/build.rs new file mode 100644 index 0000000000..57f3b626bf --- /dev/null +++ b/apps/desktop/desktop_native/objc/build.rs @@ -0,0 +1,22 @@ +use glob::glob; + +#[cfg(target_os = "macos")] +fn main() { + let mut builder = cc::Build::new(); + + // Auto compile all .m files in the src/native directory + for entry in glob("src/native/**/*.m").expect("Failed to read glob pattern") { + let path = entry.expect("Failed to read glob entry"); + builder.file(path.clone()); + println!("cargo::rerun-if-changed={}", path.display()); + } + + builder + .flag("-fobjc-arc") // Enable Auto Reference Counting (ARC) + .compile("autofill"); +} + +#[cfg(not(target_os = "macos"))] +fn main() { + // Crate is only supported on macOS +} diff --git a/apps/desktop/desktop_native/objc/src/lib.rs b/apps/desktop/desktop_native/objc/src/lib.rs new file mode 100644 index 0000000000..64475b7956 --- /dev/null +++ b/apps/desktop/desktop_native/objc/src/lib.rs @@ -0,0 +1,124 @@ +#![cfg(target_os = "macos")] + +use std::{ + ffi::{c_char, CStr, CString}, + os::raw::c_void, +}; + +use anyhow::{Context, Result}; + +#[repr(C)] +pub struct ObjCString { + value: *const c_char, + size: usize, +} + +#[repr(C)] +pub struct CommandContext { + tx: Option>, +} + +impl CommandContext { + pub fn new() -> (Self, tokio::sync::oneshot::Receiver) { + let (tx, rx) = tokio::sync::oneshot::channel::(); + + (CommandContext { tx: Some(tx) }, rx) + } + + pub fn send(&mut self, value: String) -> Result<()> { + let tx = self.tx.take().context( + "Failed to take Sender from CommandContext. Has this context already returned once?", + )?; + + tx.send(value).map_err(|_| { + anyhow::anyhow!("Failed to send ObjCString from CommandContext to Rust code") + })?; + + Ok(()) + } + + pub fn as_ptr(&mut self) -> *mut c_void { + self as *mut Self as *mut c_void + } +} + +impl TryFrom for String { + type Error = anyhow::Error; + + fn try_from(value: ObjCString) -> Result { + let c_str = unsafe { CStr::from_ptr(value.value) }; + let str = c_str + .to_str() + .context("Failed to convert ObjC output string to &str for use in Rust")?; + + Ok(str.to_owned()) + } +} + +impl Drop for ObjCString { + fn drop(&mut self) { + unsafe { + objc::freeObjCString(self); + } + } +} + +mod objc { + use std::os::raw::c_void; + + use super::*; + + extern "C" { + pub fn runCommand(context: *mut c_void, value: *const c_char); + pub fn freeObjCString(value: &ObjCString); + } + + /// This function is called from the ObjC code to return the output of the command + #[no_mangle] + pub extern "C" fn commandReturn(context: &mut CommandContext, value: ObjCString) -> bool { + let value: String = match value.try_into() { + Ok(value) => value, + Err(e) => { + println!( + "Error: Failed to convert ObjCString to Rust string during commandReturn: {}", + e + ); + + return false; + } + }; + + match context.send(value) { + Ok(_) => 0, + Err(e) => { + println!( + "Error: Failed to return ObjCString from ObjC code to Rust code: {}", + e + ); + + return false; + } + }; + + return true; + } +} + +pub async fn run_command(input: String) -> Result { + // Convert input to type that can be passed to ObjC code + let c_input = CString::new(input) + .context("Failed to convert Rust input string to a CString for use in call to ObjC code")?; + + let (mut context, rx) = CommandContext::new(); + + // Call ObjC code + unsafe { objc::runCommand(context.as_ptr(), c_input.as_ptr()) }; + + // Convert output from ObjC code to Rust string + let objc_output = rx.await?.try_into()?; + + // Convert output from ObjC code to Rust string + // let objc_output = output.try_into()?; + + Ok(objc_output) +} diff --git a/apps/desktop/desktop_native/objc/src/native/.clangd b/apps/desktop/desktop_native/objc/src/native/.clangd new file mode 100644 index 0000000000..5369bebcdc --- /dev/null +++ b/apps/desktop/desktop_native/objc/src/native/.clangd @@ -0,0 +1,2 @@ +CompileFlags: + Add: [-fobjc-arc] diff --git a/apps/desktop/desktop_native/objc/src/native/autofill/commands/status.h b/apps/desktop/desktop_native/objc/src/native/autofill/commands/status.h new file mode 100644 index 0000000000..e3e3c7969b --- /dev/null +++ b/apps/desktop/desktop_native/objc/src/native/autofill/commands/status.h @@ -0,0 +1,8 @@ +#ifndef STATUS_H +#define STATUS_H + +#import + +void status(void *context, NSDictionary *params); + +#endif diff --git a/apps/desktop/desktop_native/objc/src/native/autofill/commands/status.m b/apps/desktop/desktop_native/objc/src/native/autofill/commands/status.m new file mode 100644 index 0000000000..8811ffc6f0 --- /dev/null +++ b/apps/desktop/desktop_native/objc/src/native/autofill/commands/status.m @@ -0,0 +1,57 @@ +#import +#import +#import +#import "../../interop.h" +#import "status.h" + +void storeState(void (^callback)(ASCredentialIdentityStoreState*)) { + if (@available(macos 11, *)) { + ASCredentialIdentityStore *store = [ASCredentialIdentityStore sharedStore]; + [store getCredentialIdentityStoreStateWithCompletion:^(ASCredentialIdentityStoreState * _Nonnull state) { + callback(state); + }]; + } else { + callback(nil); + } +} + +BOOL fido2Supported() { + if (@available(macos 14, *)) { + return YES; + } else { + return NO; + } +} + +BOOL passwordSupported() { + if (@available(macos 11, *)) { + return YES; + } else { + return NO; + } +} + +void status(void* context, __attribute__((unused)) NSDictionary *params) { + storeState(^(ASCredentialIdentityStoreState *state) { + BOOL enabled = NO; + BOOL supportsIncremental = NO; + + if (state != nil) { + enabled = state.isEnabled; + supportsIncremental = state.supportsIncrementalUpdates; + } + + _return(context, + _success(@{ + @"support": @{ + @"fido2": @(fido2Supported()), + @"password": @(passwordSupported()), + @"incrementalUpdates": @(supportsIncremental), + }, + @"state": @{ + @"enabled": @(enabled), + } + }) + ); + }); +} diff --git a/apps/desktop/desktop_native/objc/src/native/autofill/commands/sync.h b/apps/desktop/desktop_native/objc/src/native/autofill/commands/sync.h new file mode 100644 index 0000000000..4eb39ff24d --- /dev/null +++ b/apps/desktop/desktop_native/objc/src/native/autofill/commands/sync.h @@ -0,0 +1,8 @@ +#ifndef SYNC_H +#define SYNC_H + +#import + +void runSync(void *context, NSDictionary *params); + +#endif diff --git a/apps/desktop/desktop_native/objc/src/native/autofill/commands/sync.m b/apps/desktop/desktop_native/objc/src/native/autofill/commands/sync.m new file mode 100644 index 0000000000..8b73635a7c --- /dev/null +++ b/apps/desktop/desktop_native/objc/src/native/autofill/commands/sync.m @@ -0,0 +1,59 @@ +#import +#import +#import +#import +#import +#import +#import "../../utils.h" +#import "../../interop.h" +#import "sync.h" + +// 'run' is added to the name because it clashes with internal macOS function +void runSync(void* context, NSDictionary *params) { + NSArray *credentials = params[@"credentials"]; + + // Map credentials to ASPasswordCredential objects + NSMutableArray *mappedCredentials = [NSMutableArray arrayWithCapacity:credentials.count]; + for (NSDictionary *credential in credentials) { + NSString *type = credential[@"type"]; + + if ([type isEqualToString:@"password"]) { + NSString *cipherId = credential[@"cipherId"]; + NSString *uri = credential[@"uri"]; + NSString *username = credential[@"username"]; + + ASCredentialServiceIdentifier *serviceId = [[ASCredentialServiceIdentifier alloc] + initWithIdentifier:uri type:ASCredentialServiceIdentifierTypeURL]; + ASPasswordCredentialIdentity *credential = [[ASPasswordCredentialIdentity alloc] + initWithServiceIdentifier:serviceId user:username recordIdentifier:cipherId]; + + [mappedCredentials addObject:credential]; + } + + if ([type isEqualToString:@"fido2"]) { + NSString *cipherId = credential[@"cipherId"]; + NSString *rpId = credential[@"rpId"]; + NSString *userName = credential[@"userName"]; + NSData *credentialId = decodeBase64URL(credential[@"credentialId"]); + NSData *userHandle = decodeBase64URL(credential[@"userHandle"]); + + ASPasskeyCredentialIdentity *credential = [[ASPasskeyCredentialIdentity alloc] + initWithRelyingPartyIdentifier:rpId + userName:userName + credentialID:credentialId + userHandle:userHandle + recordIdentifier:cipherId]; + + [mappedCredentials addObject:credential]; + } + } + + [ASCredentialIdentityStore.sharedStore replaceCredentialIdentityEntries:mappedCredentials + completion:^(__attribute__((unused)) BOOL success, NSError * _Nullable error) { + if (error) { + return _return(context, _error_er(error)); + } + + _return(context, _success(@{@"added": @([mappedCredentials count])})); + }]; +} diff --git a/apps/desktop/desktop_native/objc/src/native/autofill/run_autofill_command.h b/apps/desktop/desktop_native/objc/src/native/autofill/run_autofill_command.h new file mode 100644 index 0000000000..fba5f62686 --- /dev/null +++ b/apps/desktop/desktop_native/objc/src/native/autofill/run_autofill_command.h @@ -0,0 +1,8 @@ +#ifndef RUN_AUTOFILL_COMMAND_H +#define RUN_AUTOFILL_COMMAND_H + +#import + +void runAutofillCommand(void* context, NSDictionary *input); + +#endif diff --git a/apps/desktop/desktop_native/objc/src/native/autofill/run_autofill_command.m b/apps/desktop/desktop_native/objc/src/native/autofill/run_autofill_command.m new file mode 100644 index 0000000000..46b188139b --- /dev/null +++ b/apps/desktop/desktop_native/objc/src/native/autofill/run_autofill_command.m @@ -0,0 +1,20 @@ +#import +#import "commands/sync.h" +#import "commands/status.h" +#import "../interop.h" +#import "../utils.h" +#import "run_autofill_command.h" + +void runAutofillCommand(void* context, NSDictionary *input) { + NSString *command = input[@"command"]; + NSDictionary *params = input[@"params"]; + + if ([command isEqual:@"status"]) { + return status(context, params); + } else if ([command isEqual:@"sync"]) { + return runSync(context, params); + } + + _return(context, _error([NSString stringWithFormat:@"Unknown command: %@", command])); +} + diff --git a/apps/desktop/desktop_native/objc/src/native/interop.h b/apps/desktop/desktop_native/objc/src/native/interop.h new file mode 100644 index 0000000000..584fe547a9 --- /dev/null +++ b/apps/desktop/desktop_native/objc/src/native/interop.h @@ -0,0 +1,47 @@ +#ifndef INTEROP_H +#define INTEROP_H + +#import + +// Tips for developing Objective-C code: +// - Use the `NSLog` function to log messages to the system log +// - Example: +// NSLog(@"An example log: %@", someVariable); +// - Use the `@try` and `@catch` directives to catch exceptions + +#if !__has_feature(objc_arc) + // Auto Reference Counting makes memory management easier for Objective-C objects + // Regular C objects still need to be managed manually + #error ARC must be enabled! +#endif + +/// [Shared with Rust] +/// Simple struct to hold a C-string and its length +/// This is used to return strings created in Objective-C to Rust +/// so that Rust can free the memory when it's done with the string +struct ObjCString +{ + char *value; + size_t size; +}; + +/// [Defined in Rust] +/// External function callable from Objective-C to return a string to Rust +extern bool commandReturn(void *context, struct ObjCString output); + +/// [Callable from Rust] +/// Frees the memory allocated for an ObjCString +void freeObjCString(struct ObjCString *value); + +// --- Helper functions to convert between Objective-C and Rust types --- + +NSString *_success(NSDictionary *value); +NSString *_error(NSString *error); +NSString *_error_er(NSError *error); +NSString *_error_ex(NSException *error); +void _return(void *context, NSString *output); + +struct ObjCString nsStringToObjCString(NSString *string); +NSString *cStringToNSString(char *string); + +#endif diff --git a/apps/desktop/desktop_native/objc/src/native/interop.m b/apps/desktop/desktop_native/objc/src/native/interop.m new file mode 100644 index 0000000000..dc41eb52d7 --- /dev/null +++ b/apps/desktop/desktop_native/objc/src/native/interop.m @@ -0,0 +1,71 @@ +#import "interop.h" +#import "utils.h" + +/// [Callable from Rust] +/// Frees the memory allocated for an ObjCString +void freeObjCString(struct ObjCString *value) { + free(value->value); +} + +// --- Helper functions to convert between Objective-C and Rust types --- + +NSString *_success(NSDictionary *value) { + NSDictionary *wrapper = @{@"type": @"success", @"value": value}; + NSError *jsonError = nil; + NSString *toReturn = serializeJson(wrapper, jsonError); + + if (jsonError) { + // Manually format message since there seems to be an issue with the JSON serialization + return [NSString stringWithFormat:@"{\"type\": \"error\", \"error\": \"Error occurred while serializing error: %@\"}", jsonError]; + } + + return toReturn; +} + +NSString *_error(NSString *error) { + NSDictionary *errorDictionary = @{@"type": @"error", @"error": error}; + NSError *jsonError = nil; + NSString *toReturn = serializeJson(errorDictionary, jsonError); + + if (jsonError) { + // Manually format message since there seems to be an issue with the JSON serialization + return [NSString stringWithFormat:@"{\"type\": \"error\", \"error\": \"Error occurred while serializing error: %@\"}", jsonError]; + } + + return toReturn; +} + +NSString *_error_er(NSError *error) { + return _error([error localizedDescription]); +} + +NSString *_error_ex(NSException *error) { + return _error([NSString stringWithFormat:@"%@ (%@): %@", error.name, error.reason, [error callStackSymbols]]); +} + +void _return(void* context, NSString *output) { + if (!commandReturn(context, nsStringToObjCString(output))) { + NSLog(@"Error: Failed to return command output"); + // NOTE: This will most likely crash the application + @throw [NSException exceptionWithName:@"CommandReturnError" reason:@"Failed to return command output" userInfo:nil]; + } +} + +/// Converts an NSString to an ObjCString struct +struct ObjCString nsStringToObjCString(NSString* string) { + size_t size = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1; + char *value = malloc(size); + [string getCString:value maxLength:size encoding:NSUTF8StringEncoding]; + + struct ObjCString objCString; + objCString.value = value; + objCString.size = size; + + return objCString; +} + +/// Converts a C-string to an NSString +NSString* cStringToNSString(char* string) { + return [[NSString alloc] initWithUTF8String:string]; +} + diff --git a/apps/desktop/desktop_native/objc/src/native/run_command.m b/apps/desktop/desktop_native/objc/src/native/run_command.m new file mode 100644 index 0000000000..eb90a3340d --- /dev/null +++ b/apps/desktop/desktop_native/objc/src/native/run_command.m @@ -0,0 +1,39 @@ +#import +#import "autofill/run_autofill_command.h" +#import "interop.h" +#import "utils.h" + +void pickAndRunCommand(void* context, NSDictionary *input) { + NSString *namespace = input[@"namespace"]; + + if ([namespace isEqual:@"autofill"]) { + return runAutofillCommand(context, input); + } + + _return(context, _error([NSString stringWithFormat:@"Unknown namespace: %@", namespace])); +} + +/// [Callable from Rust] +/// Runs a command with the given input JSON +/// This function is called from Rust and is the entry point for running Objective-C code. +/// It takes a JSON string as input, deserializes it, runs the command, and serializes the output. +/// It also catches any exceptions that occur during the command execution. +void runCommand(void *context, char* inputJson) { + @autoreleasepool { + @try { + NSString *inputString = cStringToNSString(inputJson); + + NSError *error = nil; + NSDictionary *input = parseJson(inputString, error); + if (error) { + NSLog(@"Error occured while deserializing input params: %@", error); + return _return(context, _error([NSString stringWithFormat:@"Error occured while deserializing input params: %@", error])); + } + + pickAndRunCommand(context, input); + } @catch (NSException *e) { + NSLog(@"Error occurred while running Objective-C command: %@", e); + _return(context, _error([NSString stringWithFormat:@"Error occurred while running Objective-C command: %@", e])); + } + } +} diff --git a/apps/desktop/desktop_native/objc/src/native/utils.h b/apps/desktop/desktop_native/objc/src/native/utils.h new file mode 100644 index 0000000000..50fc991d15 --- /dev/null +++ b/apps/desktop/desktop_native/objc/src/native/utils.h @@ -0,0 +1,11 @@ +#ifndef UTILS_H +#define UTILS_H + +#import + +NSDictionary *parseJson(NSString *jsonString, NSError *error); +NSString *serializeJson(NSDictionary *dictionary, NSError *error); + +NSData *decodeBase64URL(NSString *base64URLString); + +#endif diff --git a/apps/desktop/desktop_native/objc/src/native/utils.m b/apps/desktop/desktop_native/objc/src/native/utils.m new file mode 100644 index 0000000000..040c723a8a --- /dev/null +++ b/apps/desktop/desktop_native/objc/src/native/utils.m @@ -0,0 +1,28 @@ +#import "utils.h" + +NSDictionary *parseJson(NSString *jsonString, NSError *error) { + NSData *data = [jsonString dataUsingEncoding:NSUTF8StringEncoding]; + NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; + if (error) { + return nil; + } + return json; +} + +NSString *serializeJson(NSDictionary *dictionary, NSError *error) { + NSData *data = [NSJSONSerialization dataWithJSONObject:dictionary options:0 error:&error]; + if (error) { + return nil; + } + return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; +} + +NSData *decodeBase64URL(NSString *base64URLString) { + NSString *base64String = [base64URLString stringByReplacingOccurrencesOfString:@"-" withString:@"+"]; + base64String = [base64String stringByReplacingOccurrencesOfString:@"_" withString:@"/"]; + + NSData *nsdataFromBase64String = [[NSData alloc] + initWithBase64EncodedString:base64String options:0]; + + return nsdataFromBase64String; +} diff --git a/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift b/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift index d5c5cabeee..4d03bf97e6 100644 --- a/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift +++ b/apps/desktop/macos/autofill-extension/CredentialProviderViewController.swift @@ -19,17 +19,18 @@ class CredentialProviderViewController: ASCredentialProviderViewController { If using the credential would require showing custom UI for authenticating the user, cancel the request with error code ASExtensionError.userInteractionRequired. - override func provideCredentialWithoutUserInteraction(for credentialIdentity: ASPasswordCredentialIdentity) { - let databaseIsUnlocked = true - if (databaseIsUnlocked) { - let passwordCredential = ASPasswordCredential(user: "j_appleseed", password: "apple1234") - self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil) - } else { - self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code:ASExtensionError.userInteractionRequired.rawValue)) - } - } - */ + */ + override func provideCredentialWithoutUserInteraction(for credentialIdentity: ASPasswordCredentialIdentity) { +// let databaseIsUnlocked = true +// if (databaseIsUnlocked) { + let passwordCredential = ASPasswordCredential(user: credentialIdentity.user, password: "example1234") + self.extensionContext.completeRequest(withSelectedCredential: passwordCredential, completionHandler: nil) +// } else { +// self.extensionContext.cancelRequest(withError: NSError(domain: ASExtensionErrorDomain, code:ASExtensionError.userInteractionRequired.rawValue)) +// } + } + /* Implement this method if provideCredentialWithoutUserInteraction(for:) can fail with ASExtensionError.userInteractionRequired. In this case, the system may present your extension's diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 54feb7df9e..eab9a7d711 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -48,6 +48,7 @@ "dist:mac": "npm run build && npm run pack:mac", "dist:mac:mas": "npm run build && npm run pack:mac:mas", "dist:mac:masdev": "npm run build && npm run pack:mac:masdev", + "dist:mac:masdev:with-extension": "npm run build && npm run pack:mac:masdev:with-extension", "dist:win": "npm run build && npm run pack:win", "dist:win:ci": "npm run build && npm run pack:win:ci", "publish:lin": "npm run build && npm run clean:dist && electron-builder --linux --x64 -p always", diff --git a/apps/desktop/scripts/build-macos-extension.js b/apps/desktop/scripts/build-macos-extension.js index 3aa43fb678..4cb643c34d 100644 --- a/apps/desktop/scripts/build-macos-extension.js +++ b/apps/desktop/scripts/build-macos-extension.js @@ -28,8 +28,9 @@ async function buildMacOs() { "-alltargets", "-configuration", "Release", - "-xcconfig", - paths.macOsConfig, + // Uncomment when signing is fixed + // "-xcconfig", + // paths.macOsConfig, ]); stdOutProc(proc); await new Promise((resolve, reject) => diff --git a/apps/desktop/src/app/services/init.service.ts b/apps/desktop/src/app/services/init.service.ts index 0068a03d68..de80f95593 100644 --- a/apps/desktop/src/app/services/init.service.ts +++ b/apps/desktop/src/app/services/init.service.ts @@ -20,6 +20,7 @@ import { VaultTimeoutService } from "@bitwarden/common/services/vault-timeout/va import { UserId } from "@bitwarden/common/types/guid"; import { KeyService as KeyServiceAbstraction } from "@bitwarden/key-management"; +import { DesktopAutofillService } from "../../autofill/services/desktop-autofill.service"; import { I18nRendererService } from "../../platform/services/i18n.renderer.service"; import { SshAgentService } from "../../platform/services/ssh-agent.service"; import { VersionService } from "../../platform/services/version.service"; @@ -45,6 +46,7 @@ export class InitService { private accountService: AccountService, private versionService: VersionService, private sshAgentService: SshAgentService, + private autofillService: DesktopAutofillService, @Inject(DOCUMENT) private document: Document, ) {} @@ -82,6 +84,8 @@ export class InitService { const containerService = new ContainerService(this.keyService, this.encryptService); containerService.attachToGlobal(this.win); + + await this.autofillService.init(); }; } } diff --git a/apps/desktop/src/app/services/services.module.ts b/apps/desktop/src/app/services/services.module.ts index 040102d039..8ca24c0040 100644 --- a/apps/desktop/src/app/services/services.module.ts +++ b/apps/desktop/src/app/services/services.module.ts @@ -48,6 +48,7 @@ import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/s import { ClientType } from "@bitwarden/common/enums"; import { ProcessReloadServiceAbstraction } from "@bitwarden/common/key-management/abstractions/process-reload.service"; import { DefaultProcessReloadService } from "@bitwarden/common/key-management/services/default-process-reload.service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; @@ -91,6 +92,7 @@ import { import { DesktopLoginApprovalComponentService } from "../../auth/login/desktop-login-approval-component.service"; import { DesktopLoginComponentService } from "../../auth/login/desktop-login-component.service"; import { DesktopAutofillSettingsService } from "../../autofill/services/desktop-autofill-settings.service"; +import { DesktopAutofillService } from "../../autofill/services/desktop-autofill.service"; import { ElectronBiometricsService } from "../../key-management/biometrics/electron-biometrics.service"; import { flagEnabled } from "../../platform/flags"; import { DesktopSettingsService } from "../../platform/services/desktop-settings.service"; @@ -301,6 +303,10 @@ const safeProviders: SafeProvider[] = [ provide: DesktopAutofillSettingsService, deps: [StateProvider], }), + safeProvider({ + provide: DesktopAutofillService, + deps: [LogService, CipherServiceAbstraction, ConfigService], + }), safeProvider({ provide: NativeMessagingManifestService, useClass: NativeMessagingManifestService, diff --git a/apps/desktop/src/autofill/preload.ts b/apps/desktop/src/autofill/preload.ts new file mode 100644 index 0000000000..9ce5e1319f --- /dev/null +++ b/apps/desktop/src/autofill/preload.ts @@ -0,0 +1,9 @@ +import { ipcRenderer } from "electron"; + +import { Command } from "../platform/main/autofill/command"; +import { RunCommandParams, RunCommandResult } from "../platform/main/autofill/native-autofill.main"; + +export default { + runCommand: (params: RunCommandParams): Promise> => + ipcRenderer.invoke("autofill.runCommand", params), +}; diff --git a/apps/desktop/src/autofill/services/desktop-autofill.service.ts b/apps/desktop/src/autofill/services/desktop-autofill.service.ts new file mode 100644 index 0000000000..8c5dd59785 --- /dev/null +++ b/apps/desktop/src/autofill/services/desktop-autofill.service.ts @@ -0,0 +1,121 @@ +import { Injectable, OnDestroy } from "@angular/core"; +import { EMPTY, Subject, distinctUntilChanged, mergeMap, switchMap, takeUntil } from "rxjs"; + +import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; +import { UriMatchStrategy } from "@bitwarden/common/models/domain/domain-service"; +import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; +import { getCredentialsForAutofill } from "@bitwarden/common/platform/services/fido2/fido2-autofill-utils"; +import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; +import { CipherType } from "@bitwarden/common/vault/enums"; +import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; + +import { NativeAutofillStatusCommand } from "../../platform/main/autofill/status.command"; +import { + NativeAutofillFido2Credential, + NativeAutofillPasswordCredential, + NativeAutofillSyncCommand, +} from "../../platform/main/autofill/sync.command"; + +@Injectable() +export class DesktopAutofillService implements OnDestroy { + private destroy$ = new Subject(); + + constructor( + private logService: LogService, + private cipherService: CipherService, + private configService: ConfigService, + ) {} + + async init() { + this.configService + .getFeatureFlag$(FeatureFlag.MacOsNativeCredentialSync) + .pipe( + distinctUntilChanged(), + switchMap((enabled) => { + if (!enabled) { + return EMPTY; + } + + return this.cipherService.cipherViews$; + }), + // TODO: This will unset all the autofill credentials on the OS + // when the account locks. We should instead explicilty clear the credentials + // when the user logs out. Maybe by subscribing to the encrypted ciphers observable instead. + mergeMap((cipherViewMap) => this.sync(Object.values(cipherViewMap ?? []))), + takeUntil(this.destroy$), + ) + .subscribe(); + } + + /** Give metadata about all available credentials in the users vault */ + async sync(cipherViews: CipherView[]) { + const status = await this.status(); + if (status.type === "error") { + return this.logService.error("Error getting autofill status", status.error); + } + + if (!status.value.state.enabled) { + // Autofill is disabled + return; + } + + let fido2Credentials: NativeAutofillFido2Credential[]; + let passwordCredentials: NativeAutofillPasswordCredential[]; + + if (status.value.support.password) { + passwordCredentials = cipherViews + .filter( + (cipher) => + cipher.type === CipherType.Login && + cipher.login.uris?.length > 0 && + cipher.login.uris.some((uri) => uri.match !== UriMatchStrategy.Never) && + cipher.login.uris.some((uri) => !Utils.isNullOrWhitespace(uri.uri)) && + !Utils.isNullOrWhitespace(cipher.login.username), + ) + .map((cipher) => ({ + type: "password", + cipherId: cipher.id, + uri: cipher.login.uris.find((uri) => uri.match !== UriMatchStrategy.Never).uri, + username: cipher.login.username, + })); + } + + if (status.value.support.fido2) { + fido2Credentials = (await getCredentialsForAutofill(cipherViews)).map((credential) => ({ + type: "fido2", + ...credential, + })); + } + + const syncResult = await ipc.autofill.runCommand({ + namespace: "autofill", + command: "sync", + params: { + credentials: [...fido2Credentials, ...passwordCredentials], + }, + }); + + if (syncResult.type === "error") { + return this.logService.error("Error syncing autofill credentials", syncResult.error); + } + + this.logService.debug(`Synced ${syncResult.value.added} autofill credentials`); + } + + /** Get autofill status from OS */ + private status() { + // TODO: Investigate why this type needs to be explicitly set + return ipc.autofill.runCommand({ + namespace: "autofill", + command: "status", + params: {}, + }); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } +} diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index bbb5c23513..5ae72150ff 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -35,6 +35,7 @@ import { PowerMonitorMain } from "./main/power-monitor.main"; import { TrayMain } from "./main/tray.main"; import { UpdaterMain } from "./main/updater.main"; import { WindowMain } from "./main/window.main"; +import { NativeAutofillMain } from "./platform/main/autofill/native-autofill.main"; import { ClipboardMain } from "./platform/main/clipboard.main"; import { DesktopCredentialStorageListener } from "./platform/main/desktop-credential-storage-listener"; import { MainCryptoFunctionService } from "./platform/main/main-crypto-function.service"; @@ -72,6 +73,7 @@ export class Main { biometricsService: DesktopBiometricsService; nativeMessagingMain: NativeMessagingMain; clipboardMain: ClipboardMain; + nativeAutofillMain: NativeAutofillMain; desktopAutofillSettingsService: DesktopAutofillSettingsService; versionMain: VersionMain; sshAgentService: MainSshAgentService; @@ -256,6 +258,9 @@ export class Main { new EphemeralValueStorageService(); new SSOLocalhostCallbackService(this.environmentService, this.messagingService); + + this.nativeAutofillMain = new NativeAutofillMain(this.logService); + void this.nativeAutofillMain.init(); } bootstrap() { diff --git a/apps/desktop/src/platform/main/autofill/command.ts b/apps/desktop/src/platform/main/autofill/command.ts new file mode 100644 index 0000000000..a8b5548052 --- /dev/null +++ b/apps/desktop/src/platform/main/autofill/command.ts @@ -0,0 +1,23 @@ +import { NativeAutofillStatusCommand } from "./status.command"; +import { NativeAutofillSyncCommand } from "./sync.command"; + +export type CommandDefinition = { + namespace: string; + name: string; + input: Record; + output: Record; +}; + +export type CommandOutput = + | { + type: "error"; + error: string; + } + | { type: "success"; value: SuccessOutput }; + +export type IpcCommandInvoker = ( + params: C["input"], +) => Promise>; + +/** A list of all available commands */ +export type Command = NativeAutofillSyncCommand | NativeAutofillStatusCommand; diff --git a/apps/desktop/src/platform/main/autofill/native-autofill.main.ts b/apps/desktop/src/platform/main/autofill/native-autofill.main.ts new file mode 100644 index 0000000000..b4b7895e8a --- /dev/null +++ b/apps/desktop/src/platform/main/autofill/native-autofill.main.ts @@ -0,0 +1,53 @@ +import { ipcMain } from "electron"; + +import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { autofill } from "@bitwarden/desktop-napi"; + +import { CommandDefinition } from "./command"; + +export type RunCommandParams = { + namespace: C["namespace"]; + command: C["name"]; + params: C["input"]; +}; + +export type RunCommandResult = C["output"]; + +export class NativeAutofillMain { + constructor(private logService: LogService) {} + + async init() { + ipcMain.handle( + "autofill.runCommand", + ( + _event: any, + params: RunCommandParams, + ): Promise> => { + return this.runCommand(params); + }, + ); + } + + private async runCommand( + command: RunCommandParams, + ): Promise> { + try { + const result = await autofill.runCommand(JSON.stringify(command)); + const parsed = JSON.parse(result) as RunCommandResult; + + if (parsed.type === "error") { + this.logService.error(`Error running autofill command '${command.command}':`, parsed.error); + } + + return parsed; + } catch (e) { + this.logService.error(`Error running autofill command '${command.command}':`, e); + + if (e instanceof Error) { + return { type: "error", error: e.stack ?? String(e) } as RunCommandResult; + } + + return { type: "error", error: String(e) } as RunCommandResult; + } + } +} diff --git a/apps/desktop/src/platform/main/autofill/status.command.ts b/apps/desktop/src/platform/main/autofill/status.command.ts new file mode 100644 index 0000000000..b6c0943fa6 --- /dev/null +++ b/apps/desktop/src/platform/main/autofill/status.command.ts @@ -0,0 +1,20 @@ +import { CommandDefinition, CommandOutput } from "./command"; + +export interface NativeAutofillStatusCommand extends CommandDefinition { + name: "status"; + input: NativeAutofillStatusParams; + output: NativeAutofillStatusResult; +} + +export type NativeAutofillStatusParams = Record; + +export type NativeAutofillStatusResult = CommandOutput<{ + support: { + fido2: boolean; + password: boolean; + incrementalUpdates: boolean; + }; + state: { + enabled: boolean; + }; +}>; diff --git a/apps/desktop/src/platform/main/autofill/sync.command.ts b/apps/desktop/src/platform/main/autofill/sync.command.ts new file mode 100644 index 0000000000..dc3b12383e --- /dev/null +++ b/apps/desktop/src/platform/main/autofill/sync.command.ts @@ -0,0 +1,37 @@ +import { CommandDefinition, CommandOutput } from "./command"; + +export interface NativeAutofillSyncCommand extends CommandDefinition { + name: "sync"; + input: NativeAutofillSyncParams; + output: NativeAutofillSyncResult; +} + +export type NativeAutofillSyncParams = { + credentials: NativeAutofillCredential[]; +}; + +export type NativeAutofillCredential = + | NativeAutofillFido2Credential + | NativeAutofillPasswordCredential; + +export type NativeAutofillFido2Credential = { + type: "fido2"; + cipherId: string; + rpId: string; + userName: string; + /** Should be Base64URL-encoded binary data */ + credentialId: string; + /** Should be Base64URL-encoded binary data */ + userHandle: string; +}; + +export type NativeAutofillPasswordCredential = { + type: "password"; + cipherId: string; + uri: string; + username: string; +}; + +export type NativeAutofillSyncResult = CommandOutput<{ + added: number; +}>; diff --git a/apps/desktop/src/preload.ts b/apps/desktop/src/preload.ts index 57b20490a4..0fb2db3751 100644 --- a/apps/desktop/src/preload.ts +++ b/apps/desktop/src/preload.ts @@ -1,6 +1,7 @@ import { contextBridge } from "electron"; import auth from "./auth/preload"; +import autofill from "./autofill/preload"; import keyManagement from "./key-management/preload"; import platform from "./platform/preload"; @@ -17,6 +18,7 @@ import platform from "./platform/preload"; // Each team owns a subspace of the `ipc` global variable in the renderer. export const ipc = { auth, + autofill, platform, keyManagement, }; diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts index 66d6b155e9..f79ebf8aa5 100644 --- a/libs/common/src/enums/feature-flag.enum.ts +++ b/libs/common/src/enums/feature-flag.enum.ts @@ -38,6 +38,7 @@ export enum FeatureFlag { NewDeviceVerificationTemporaryDismiss = "new-device-temporary-dismiss", NewDeviceVerificationPermanentDismiss = "new-device-permanent-dismiss", DisableFreeFamiliesSponsorship = "PM-12274-disable-free-families-sponsorship", + MacOsNativeCredentialSync = "macos-native-credential-sync", PM11360RemoveProviderExportPermission = "pm-11360-remove-provider-export-permission", } @@ -87,6 +88,7 @@ export const DefaultFeatureFlagValue = { [FeatureFlag.NewDeviceVerificationTemporaryDismiss]: FALSE, [FeatureFlag.NewDeviceVerificationPermanentDismiss]: FALSE, [FeatureFlag.DisableFreeFamiliesSponsorship]: FALSE, + [FeatureFlag.MacOsNativeCredentialSync]: FALSE, [FeatureFlag.PM11360RemoveProviderExportPermission]: FALSE, } satisfies Record; diff --git a/libs/common/src/platform/services/fido2/fido2-autofill-utils.ts b/libs/common/src/platform/services/fido2/fido2-autofill-utils.ts new file mode 100644 index 0000000000..7ef705b95f --- /dev/null +++ b/libs/common/src/platform/services/fido2/fido2-autofill-utils.ts @@ -0,0 +1,26 @@ +// TODO: Add tests for this method + +import { CipherType } from "../../../vault/enums"; +import { CipherView } from "../../../vault/models/view/cipher.view"; +import { Fido2CredentialAutofillView } from "../../../vault/models/view/fido2-credential-autofill.view"; + +// TODO: Move into Fido2AuthenticatorService +export async function getCredentialsForAutofill( + ciphers: CipherView[], +): Promise { + return ciphers + .filter( + (cipher) => + !cipher.isDeleted && cipher.type === CipherType.Login && cipher.login.hasFido2Credentials, + ) + .map((cipher) => { + const credential = cipher.login.fido2Credentials[0]; + return { + cipherId: cipher.id, + credentialId: credential.credentialId, + rpId: credential.rpId, + userHandle: credential.userHandle, + userName: credential.userName, + }; + }); +} diff --git a/libs/common/src/vault/models/view/fido2-credential-autofill.view.ts b/libs/common/src/vault/models/view/fido2-credential-autofill.view.ts new file mode 100644 index 0000000000..b120f27b77 --- /dev/null +++ b/libs/common/src/vault/models/view/fido2-credential-autofill.view.ts @@ -0,0 +1,7 @@ +export class Fido2CredentialAutofillView { + cipherId: string; + credentialId: string; + rpId: string; + userHandle: string; + userName: string; +} From 7e2456224d0fab7b1c975e555523aa055d04bb7a Mon Sep 17 00:00:00 2001 From: Opeyemi Date: Fri, 6 Dec 2024 16:52:31 +0000 Subject: [PATCH 005/112] [BRE-470] - Update Renovate Conf for BRE team (#12274) --- .github/renovate.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/renovate.json b/.github/renovate.json index 9295ea5d61..7f3e7464fe 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -10,7 +10,7 @@ }, { "matchManagers": ["github-actions"], - "commitMessagePrefix": "[deps] DevOps:" + "commitMessagePrefix": "[deps] BRE:" }, { "matchManagers": ["cargo"], From 9fcc4f0543fce95c90b10616fff13e3a273d0457 Mon Sep 17 00:00:00 2001 From: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:43:17 -0500 Subject: [PATCH 006/112] PM-13659 - 2FA Timeout Log All the things (#12275) --- .../auth/components/two-factor.component.ts | 10 ++++++ .../abstractions/login-strategy.service.ts | 2 ++ .../login-strategy.service.ts | 36 ++++++++++++++++++- 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/libs/angular/src/auth/components/two-factor.component.ts b/libs/angular/src/auth/components/two-factor.component.ts index 33269e28e9..f484ccd1e8 100644 --- a/libs/angular/src/auth/components/two-factor.component.ts +++ b/libs/angular/src/auth/components/two-factor.component.ts @@ -102,10 +102,20 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI super(environmentService, i18nService, platformUtilsService, toastService); this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win); + this.logService.info( + "Subscribing to timeout on LoginStrategyService with service id: " + + this.loginStrategyService.id, + ); + // Add subscription to twoFactorTimeout$ and navigate to twoFactorTimeoutRoute if expired this.loginStrategyService.twoFactorTimeout$ .pipe(takeUntilDestroyed()) .subscribe(async (expired) => { + this.logService.info( + "Received emission from LoginStrategyService.twoFactorTimeout$ with service id: " + + this.loginStrategyService.id, + ); + if (!expired) { return; } diff --git a/libs/auth/src/common/abstractions/login-strategy.service.ts b/libs/auth/src/common/abstractions/login-strategy.service.ts index e86cd6b0b0..89e8491b27 100644 --- a/libs/auth/src/common/abstractions/login-strategy.service.ts +++ b/libs/auth/src/common/abstractions/login-strategy.service.ts @@ -14,6 +14,8 @@ import { } from "../models/domain/login-credentials"; export abstract class LoginStrategyServiceAbstraction { + id: string; + /** * The current strategy being used to authenticate. * Emits null if the session has timed out. diff --git a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts index 1d5001f1f0..0c857cf7cc 100644 --- a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts +++ b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts @@ -7,6 +7,7 @@ import { shareReplay, Subscription, BehaviorSubject, + tap, } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; @@ -31,6 +32,7 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; import { TaskSchedulerService, ScheduledTaskNames } from "@bitwarden/common/platform/scheduling"; import { GlobalState, GlobalStateProvider } from "@bitwarden/common/platform/state"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/src/auth/abstractions/device-trust.service.abstraction"; @@ -81,7 +83,36 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { private authRequestPushNotificationState: GlobalState; private twoFactorTimeoutSubject = new BehaviorSubject(false); - twoFactorTimeout$: Observable = this.twoFactorTimeoutSubject.asObservable(); + twoFactorTimeout$: Observable = this.twoFactorTimeoutSubject.asObservable().pipe( + // line 87 is the tap? + tap({ + next: (value) => { + this.logService.info( + `LoginStrategyService.twoFactorTimeout$ with service id: ${this.id} emmitted value: ${value}`, + ); + }, + error: (error: unknown) => { + this.logService.error( + `LoginStrategyService.twoFactorTimeout$ with service id: ${this.id} errored with error: ${JSON.stringify(error)}`, + ); + }, + finalize: () => { + this.logService.info( + `LoginStrategyService.twoFactorTimeout$ with service id: ${this.id} finalized`, + ); + }, + complete: () => { + this.logService.info( + `LoginStrategyService.twoFactorTimeout$ with service id: ${this.id} completed`, + ); + }, + subscribe: () => { + this.logService.info( + `LoginStrategyService.twoFactorTimeout$ with service id: ${this.id} subscribed`, + ); + }, + }), + ); private loginStrategy$: Observable< | UserApiLoginStrategy @@ -94,6 +125,8 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { currentAuthType$: Observable; + id: string = Utils.newGuid(); + constructor( protected accountService: AccountService, protected masterPasswordService: InternalMasterPasswordServiceAbstraction, @@ -131,6 +164,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { this.taskSchedulerService.registerTaskHandler( ScheduledTaskNames.loginStrategySessionTimeout, async () => { + this.logService.info("Timeout executing for LoginStrategyService with id: " + this.id); this.authnSessionTimeoutExecutor(async () => { this.twoFactorTimeoutSubject.next(true); try { From 597e3855619468ec662323c9eefcd527cbbaa22a Mon Sep 17 00:00:00 2001 From: rr-bw <102181210+rr-bw@users.noreply.github.com> Date: Fri, 6 Dec 2024 10:48:58 -0800 Subject: [PATCH 007/112] update height of iframe on web (#12265) --- apps/web/src/scss/plugins.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/scss/plugins.scss b/apps/web/src/scss/plugins.scss index f4aa428532..5fbd32ac4e 100644 --- a/apps/web/src/scss/plugins.scss +++ b/apps/web/src/scss/plugins.scss @@ -12,7 +12,7 @@ } #web-authn-frame { - height: 290px; + height: 315px; @include themify($themes) { background: themed("imgLoading") 0 0 no-repeat; } From d295825ff11943ba348415b3d90eeec8f8df8a64 Mon Sep 17 00:00:00 2001 From: Jordan Aasen <166539328+jaasen-livefront@users.noreply.github.com> Date: Fri, 6 Dec 2024 11:29:35 -0800 Subject: [PATCH 008/112] [CL-511] - add variant to links within banners (#12090) * add variant to links within banners * add fix prop --- .../payment-method/organization-payment-method.component.html | 2 +- apps/web/src/app/billing/shared/payment-method.component.html | 2 +- .../individual-vault/vault-banners/vault-banners.component.html | 2 +- apps/web/src/app/vault/org-vault/vault.component.html | 2 +- .../src/app/secrets-manager/overview/overview.component.html | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.html b/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.html index 7a6e8558ba..e7bc9cac01 100644 --- a/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.html +++ b/apps/web/src/app/billing/organizations/payment-method/organization-payment-method.component.html @@ -9,7 +9,7 @@ {{ freeTrialData.message }} Date: Fri, 6 Dec 2024 13:42:32 -0600 Subject: [PATCH 009/112] revert: [PR-13659] remove 2FA timeout logging and fix attempts This reverts two previous commits: - PM-13659 - 2FA Timeout Log All the things (#12275) - Auth/PM-13659 - 2FA Timeout - Attempted Fix (#12263) --- .../service-container/service-container.ts | 7 --- .../auth/components/two-factor.component.ts | 10 ---- libs/angular/src/services/injection-tokens.ts | 6 +-- .../src/services/jslib-services.module.ts | 9 +--- .../abstractions/login-strategy.service.ts | 2 - .../login-strategy.service.ts | 53 +++---------------- 6 files changed, 9 insertions(+), 78 deletions(-) diff --git a/apps/cli/src/service-container/service-container.ts b/apps/cli/src/service-container/service-container.ts index ce944db78f..21e8f9f208 100644 --- a/apps/cli/src/service-container/service-container.ts +++ b/apps/cli/src/service-container/service-container.ts @@ -17,7 +17,6 @@ import { PinService, PinServiceAbstraction, UserDecryptionOptionsService, - Executor, } from "@bitwarden/auth/common"; import { EventCollectionService as EventCollectionServiceAbstraction } from "@bitwarden/common/abstractions/event/event-collection.service"; import { EventUploadService as EventUploadServiceAbstraction } from "@bitwarden/common/abstractions/event/event-upload.service"; @@ -615,11 +614,6 @@ export class ServiceContainer { this.configService, ); - // Execute any authn session timeout logic without any wrapping logic. - // An executor is required to ensure the logic is executed in an Angular context when it - // it is available. - const authnSessionTimeoutExecutor: Executor = (fn) => fn(); - this.loginStrategyService = new LoginStrategyService( this.accountService, this.masterPasswordService, @@ -646,7 +640,6 @@ export class ServiceContainer { this.vaultTimeoutSettingsService, this.kdfConfigService, this.taskSchedulerService, - authnSessionTimeoutExecutor, ); // FIXME: CLI does not support autofill diff --git a/libs/angular/src/auth/components/two-factor.component.ts b/libs/angular/src/auth/components/two-factor.component.ts index f484ccd1e8..33269e28e9 100644 --- a/libs/angular/src/auth/components/two-factor.component.ts +++ b/libs/angular/src/auth/components/two-factor.component.ts @@ -102,20 +102,10 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI super(environmentService, i18nService, platformUtilsService, toastService); this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win); - this.logService.info( - "Subscribing to timeout on LoginStrategyService with service id: " + - this.loginStrategyService.id, - ); - // Add subscription to twoFactorTimeout$ and navigate to twoFactorTimeoutRoute if expired this.loginStrategyService.twoFactorTimeout$ .pipe(takeUntilDestroyed()) .subscribe(async (expired) => { - this.logService.info( - "Received emission from LoginStrategyService.twoFactorTimeout$ with service id: " + - this.loginStrategyService.id, - ); - if (!expired) { return; } diff --git a/libs/angular/src/services/injection-tokens.ts b/libs/angular/src/services/injection-tokens.ts index 69a1ed3f61..86c5642a0c 100644 --- a/libs/angular/src/services/injection-tokens.ts +++ b/libs/angular/src/services/injection-tokens.ts @@ -1,7 +1,7 @@ import { InjectionToken } from "@angular/core"; import { Observable, Subject } from "rxjs"; -import { Executor, LogoutReason } from "@bitwarden/auth/common"; +import { LogoutReason } from "@bitwarden/auth/common"; import { ClientType } from "@bitwarden/common/enums"; import { RegionConfig } from "@bitwarden/common/platform/abstractions/environment.service"; import { @@ -68,7 +68,3 @@ export const REFRESH_ACCESS_TOKEN_ERROR_CALLBACK = new SafeInjectionToken<() => export const ENV_ADDITIONAL_REGIONS = new SafeInjectionToken( "ENV_ADDITIONAL_REGIONS", ); - -export const AUTHN_SESSION_TIMEOUT_EXECUTOR = new SafeInjectionToken( - "AuthnSessionTimeoutExecutor", -); diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 8637e26a2b..a43f1fa07a 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -1,4 +1,4 @@ -import { ErrorHandler, LOCALE_ID, NgModule, NgZone } from "@angular/core"; +import { ErrorHandler, LOCALE_ID, NgModule } from "@angular/core"; import { Subject } from "rxjs"; import { @@ -319,7 +319,6 @@ import { CLIENT_TYPE, REFRESH_ACCESS_TOKEN_ERROR_CALLBACK, ENV_ADDITIONAL_REGIONS, - AUTHN_SESSION_TIMEOUT_EXECUTOR, } from "./injection-tokens"; import { ModalService } from "./modal.service"; @@ -412,11 +411,6 @@ const safeProviders: SafeProvider[] = [ TokenServiceAbstraction, ], }), - safeProvider({ - provide: AUTHN_SESSION_TIMEOUT_EXECUTOR, - useFactory: (ngZone: NgZone) => (fn: () => void) => ngZone.run(fn), - deps: [NgZone], - }), safeProvider({ provide: LoginStrategyServiceAbstraction, useClass: LoginStrategyService, @@ -446,7 +440,6 @@ const safeProviders: SafeProvider[] = [ VaultTimeoutSettingsServiceAbstraction, KdfConfigService, TaskSchedulerService, - AUTHN_SESSION_TIMEOUT_EXECUTOR, ], }), safeProvider({ diff --git a/libs/auth/src/common/abstractions/login-strategy.service.ts b/libs/auth/src/common/abstractions/login-strategy.service.ts index 89e8491b27..e86cd6b0b0 100644 --- a/libs/auth/src/common/abstractions/login-strategy.service.ts +++ b/libs/auth/src/common/abstractions/login-strategy.service.ts @@ -14,8 +14,6 @@ import { } from "../models/domain/login-credentials"; export abstract class LoginStrategyServiceAbstraction { - id: string; - /** * The current strategy being used to authenticate. * Emits null if the session has timed out. diff --git a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts index 0c857cf7cc..99e3c057e1 100644 --- a/libs/auth/src/common/services/login-strategies/login-strategy.service.ts +++ b/libs/auth/src/common/services/login-strategies/login-strategy.service.ts @@ -7,7 +7,6 @@ import { shareReplay, Subscription, BehaviorSubject, - tap, } from "rxjs"; import { ApiService } from "@bitwarden/common/abstractions/api.service"; @@ -32,7 +31,6 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service" import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; import { TaskSchedulerService, ScheduledTaskNames } from "@bitwarden/common/platform/scheduling"; import { GlobalState, GlobalStateProvider } from "@bitwarden/common/platform/state"; import { DeviceTrustServiceAbstraction } from "@bitwarden/common/src/auth/abstractions/device-trust.service.abstraction"; @@ -73,8 +71,6 @@ import { const sessionTimeoutLength = 5 * 60 * 1000; // 5 minutes -export type Executor = (fn: () => void) => void; - export class LoginStrategyService implements LoginStrategyServiceAbstraction { private sessionTimeoutSubscription: Subscription; private currentAuthnTypeState: GlobalState; @@ -83,36 +79,7 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { private authRequestPushNotificationState: GlobalState; private twoFactorTimeoutSubject = new BehaviorSubject(false); - twoFactorTimeout$: Observable = this.twoFactorTimeoutSubject.asObservable().pipe( - // line 87 is the tap? - tap({ - next: (value) => { - this.logService.info( - `LoginStrategyService.twoFactorTimeout$ with service id: ${this.id} emmitted value: ${value}`, - ); - }, - error: (error: unknown) => { - this.logService.error( - `LoginStrategyService.twoFactorTimeout$ with service id: ${this.id} errored with error: ${JSON.stringify(error)}`, - ); - }, - finalize: () => { - this.logService.info( - `LoginStrategyService.twoFactorTimeout$ with service id: ${this.id} finalized`, - ); - }, - complete: () => { - this.logService.info( - `LoginStrategyService.twoFactorTimeout$ with service id: ${this.id} completed`, - ); - }, - subscribe: () => { - this.logService.info( - `LoginStrategyService.twoFactorTimeout$ with service id: ${this.id} subscribed`, - ); - }, - }), - ); + twoFactorTimeout$: Observable = this.twoFactorTimeoutSubject.asObservable(); private loginStrategy$: Observable< | UserApiLoginStrategy @@ -125,8 +92,6 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { currentAuthType$: Observable; - id: string = Utils.newGuid(); - constructor( protected accountService: AccountService, protected masterPasswordService: InternalMasterPasswordServiceAbstraction, @@ -153,7 +118,6 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { protected vaultTimeoutSettingsService: VaultTimeoutSettingsService, protected kdfConfigService: KdfConfigService, protected taskSchedulerService: TaskSchedulerService, - private authnSessionTimeoutExecutor: Executor = (fn) => fn(), // Default to no-op ) { this.currentAuthnTypeState = this.stateProvider.get(CURRENT_LOGIN_STRATEGY_KEY); this.loginStrategyCacheState = this.stateProvider.get(CACHE_KEY); @@ -164,15 +128,12 @@ export class LoginStrategyService implements LoginStrategyServiceAbstraction { this.taskSchedulerService.registerTaskHandler( ScheduledTaskNames.loginStrategySessionTimeout, async () => { - this.logService.info("Timeout executing for LoginStrategyService with id: " + this.id); - this.authnSessionTimeoutExecutor(async () => { - this.twoFactorTimeoutSubject.next(true); - try { - await this.clearCache(); - } catch (e) { - this.logService.error("Failed to clear cache during session timeout", e); - } - }); + this.twoFactorTimeoutSubject.next(true); + try { + await this.clearCache(); + } catch (e) { + this.logService.error("Failed to clear cache during session timeout", e); + } }, ); From 019a4d16c477ab67242ecc08cfaf65ca1579d144 Mon Sep 17 00:00:00 2001 From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Date: Fri, 6 Dec 2024 23:23:18 +0100 Subject: [PATCH 010/112] Resolve the Unauthorized issue on secrets manager (#12278) --- .../src/app/secrets-manager/overview/overview.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts index 3585f09faf..534b1c4ea3 100644 --- a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts +++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.ts @@ -140,7 +140,7 @@ export class OverviewComponent implements OnInit, OnDestroy { }); this.freeTrial$ = org$.pipe( - filter((org) => org.isOwner), + filter((org) => org.isOwner && org.canViewBillingHistory && org.canViewSubscription), switchMap((org) => combineLatest([ of(org), From a05162b373bca13ddad84765dec465b3b1734880 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 19:09:05 -0600 Subject: [PATCH 011/112] [deps] AC: Update mini-css-extract-plugin to v2.9.2 (#11937) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- 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 36b494c00c..584a19adcd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -155,7 +155,7 @@ "jest-mock-extended": "3.0.7", "jest-preset-angular": "14.1.1", "lint-staged": "15.2.8", - "mini-css-extract-plugin": "2.9.1", + "mini-css-extract-plugin": "2.9.2", "node-ipc": "9.2.1", "postcss": "8.4.47", "postcss-loader": "8.1.1", @@ -24579,9 +24579,9 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.1.tgz", - "integrity": "sha512-+Vyi+GCCOHnrJ2VPS+6aPoXN2k2jgUzDRhTFLjjTBn23qyXJXkjUWQgTL+mXpF5/A8ixLdCc6kWsoeOjKGejKQ==", + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.2.tgz", + "integrity": "sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 7d418b27d0..4574e94d19 100644 --- a/package.json +++ b/package.json @@ -116,7 +116,7 @@ "jest-mock-extended": "3.0.7", "jest-preset-angular": "14.1.1", "lint-staged": "15.2.8", - "mini-css-extract-plugin": "2.9.1", + "mini-css-extract-plugin": "2.9.2", "node-ipc": "9.2.1", "postcss": "8.4.47", "postcss-loader": "8.1.1", From c6eb78d7fc08d49578e4c77396961b46b71da5f8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 19:42:56 -0600 Subject: [PATCH 012/112] [deps] AC: Update postcss to v8.4.49 (#11938) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 10 +++++----- package.json | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 584a19adcd..f1bc7e0c80 100644 --- a/package-lock.json +++ b/package-lock.json @@ -157,7 +157,7 @@ "lint-staged": "15.2.8", "mini-css-extract-plugin": "2.9.2", "node-ipc": "9.2.1", - "postcss": "8.4.47", + "postcss": "8.4.49", "postcss-loader": "8.1.1", "prettier": "3.3.3", "prettier-plugin-tailwindcss": "0.6.9", @@ -27224,9 +27224,9 @@ } }, "node_modules/postcss": { - "version": "8.4.47", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", - "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", "dev": true, "funding": [ { @@ -27245,7 +27245,7 @@ "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.1.0", + "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, "engines": { diff --git a/package.json b/package.json index 4574e94d19..1915aef2e9 100644 --- a/package.json +++ b/package.json @@ -118,7 +118,7 @@ "lint-staged": "15.2.8", "mini-css-extract-plugin": "2.9.2", "node-ipc": "9.2.1", - "postcss": "8.4.47", + "postcss": "8.4.49", "postcss-loader": "8.1.1", "prettier": "3.3.3", "prettier-plugin-tailwindcss": "0.6.9", From 1654ff9b7adc019c47626e2d2ffae26c0682574d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 19:56:05 -0600 Subject: [PATCH 013/112] [deps] AC: Update sass-loader to v16.0.4 (#11309) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- 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 f1bc7e0c80..fa2484612e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -165,7 +165,7 @@ "remark-gfm": "4.0.0", "rimraf": "6.0.1", "sass": "1.81.0", - "sass-loader": "16.0.1", + "sass-loader": "16.0.4", "storybook": "8.4.5", "style-loader": "3.3.4", "tailwindcss": "3.4.15", @@ -29135,9 +29135,9 @@ } }, "node_modules/sass-loader": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.1.tgz", - "integrity": "sha512-xACl1ToTsKnL9Ce5yYpRxrLj9QUDCnwZNhzpC7tKiFyA8zXsd3Ap+HGVnbCgkdQcm43E+i6oKAWBsvGA6ZoiMw==", + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-16.0.4.tgz", + "integrity": "sha512-LavLbgbBGUt3wCiYzhuLLu65+fWXaXLmq7YxivLhEqmiupCFZ5sKUAipK3do6V80YSU0jvSxNhEdT13IXNr3rg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 1915aef2e9..e5a595a3f8 100644 --- a/package.json +++ b/package.json @@ -126,7 +126,7 @@ "remark-gfm": "4.0.0", "rimraf": "6.0.1", "sass": "1.81.0", - "sass-loader": "16.0.1", + "sass-loader": "16.0.4", "storybook": "8.4.5", "style-loader": "3.3.4", "tailwindcss": "3.4.15", From d5af932cef43066e91ca9ea145afe67303f27cf2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 23:17:47 -0600 Subject: [PATCH 014/112] [deps] AC: Update style-loader to v4 (#10589) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package-lock.json | 29 +++++++++++++++++++++++------ package.json | 2 +- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index fa2484612e..1abea1dbcf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -167,7 +167,7 @@ "sass": "1.81.0", "sass-loader": "16.0.4", "storybook": "8.4.5", - "style-loader": "3.3.4", + "style-loader": "4.0.0", "tailwindcss": "3.4.15", "ts-jest": "29.2.2", "ts-loader": "9.5.1", @@ -9098,6 +9098,23 @@ } } }, + "node_modules/@storybook/builder-webpack5/node_modules/style-loader": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz", + "integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, "node_modules/@storybook/components": { "version": "8.4.5", "resolved": "https://registry.npmjs.org/@storybook/components/-/components-8.4.5.tgz", @@ -30494,20 +30511,20 @@ } }, "node_modules/style-loader": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.4.tgz", - "integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz", + "integrity": "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==", "dev": true, "license": "MIT", "engines": { - "node": ">= 12.13.0" + "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "^5.0.0" + "webpack": "^5.27.0" } }, "node_modules/sucrase": { diff --git a/package.json b/package.json index e5a595a3f8..68017a9236 100644 --- a/package.json +++ b/package.json @@ -128,7 +128,7 @@ "sass": "1.81.0", "sass-loader": "16.0.4", "storybook": "8.4.5", - "style-loader": "3.3.4", + "style-loader": "4.0.0", "tailwindcss": "3.4.15", "ts-jest": "29.2.2", "ts-loader": "9.5.1", From 3bfe5e4a65ab643813127b1bf32ec12468a05009 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 9 Dec 2024 02:09:57 -0800 Subject: [PATCH 015/112] [PM-13099] Enable browserintegration on dmg builds on adding an env variable (#11359) * Enable browserintegration on dmg builds on adding an env variable * Fix crash * Cleanup --- .../src/app/accounts/settings.component.ts | 76 ++++++++++--------- apps/desktop/src/platform/preload.ts | 11 ++- apps/desktop/src/utils.ts | 10 +++ 3 files changed, 62 insertions(+), 35 deletions(-) diff --git a/apps/desktop/src/app/accounts/settings.component.ts b/apps/desktop/src/app/accounts/settings.component.ts index a8ce45f53c..387e6823f3 100644 --- a/apps/desktop/src/app/accounts/settings.component.ts +++ b/apps/desktop/src/app/accounts/settings.component.ts @@ -632,43 +632,51 @@ export class SettingsComponent implements OnInit, OnDestroy { } async saveBrowserIntegration() { - if ( - ipc.platform.deviceType === DeviceType.MacOsDesktop && - !this.platformUtilsService.isMacAppStore() && - !ipc.platform.isDev - ) { - await this.dialogService.openSimpleDialog({ - title: { key: "browserIntegrationUnsupportedTitle" }, - content: { key: "browserIntegrationMasOnlyDesc" }, - acceptButtonText: { key: "ok" }, - cancelButtonText: null, - type: "warning", - }); + const skipSupportedPlatformCheck = + ipc.platform.allowBrowserintegrationOverride || ipc.platform.isDev; - this.form.controls.enableBrowserIntegration.setValue(false); - return; - } else if (ipc.platform.isWindowsStore) { - await this.dialogService.openSimpleDialog({ - title: { key: "browserIntegrationUnsupportedTitle" }, - content: { key: "browserIntegrationWindowsStoreDesc" }, - acceptButtonText: { key: "ok" }, - cancelButtonText: null, - type: "warning", - }); + if (skipSupportedPlatformCheck) { + if ( + ipc.platform.deviceType === DeviceType.MacOsDesktop && + !this.platformUtilsService.isMacAppStore() + ) { + await this.dialogService.openSimpleDialog({ + title: { key: "browserIntegrationUnsupportedTitle" }, + content: { key: "browserIntegrationMasOnlyDesc" }, + acceptButtonText: { key: "ok" }, + cancelButtonText: null, + type: "warning", + }); - this.form.controls.enableBrowserIntegration.setValue(false); - return; - } else if (ipc.platform.isSnapStore || ipc.platform.isFlatpak) { - await this.dialogService.openSimpleDialog({ - title: { key: "browserIntegrationUnsupportedTitle" }, - content: { key: "browserIntegrationLinuxDesc" }, - acceptButtonText: { key: "ok" }, - cancelButtonText: null, - type: "warning", - }); + this.form.controls.enableBrowserIntegration.setValue(false); + return; + } - this.form.controls.enableBrowserIntegration.setValue(false); - return; + if (ipc.platform.isWindowsStore) { + await this.dialogService.openSimpleDialog({ + title: { key: "browserIntegrationUnsupportedTitle" }, + content: { key: "browserIntegrationWindowsStoreDesc" }, + acceptButtonText: { key: "ok" }, + cancelButtonText: null, + type: "warning", + }); + + this.form.controls.enableBrowserIntegration.setValue(false); + return; + } + + if (ipc.platform.isSnapStore || ipc.platform.isFlatpak) { + await this.dialogService.openSimpleDialog({ + title: { key: "browserIntegrationUnsupportedTitle" }, + content: { key: "browserIntegrationLinuxDesc" }, + acceptButtonText: { key: "ok" }, + cancelButtonText: null, + type: "warning", + }); + + this.form.controls.enableBrowserIntegration.setValue(false); + return; + } } await this.desktopSettingsService.setBrowserIntegrationEnabled( diff --git a/apps/desktop/src/platform/preload.ts b/apps/desktop/src/platform/preload.ts index 30e248d352..0b61d89477 100644 --- a/apps/desktop/src/platform/preload.ts +++ b/apps/desktop/src/platform/preload.ts @@ -11,7 +11,15 @@ import { Message, UnencryptedMessageResponse, } from "../models/native-messaging"; -import { isAppImage, isDev, isFlatpak, isMacAppStore, isSnapStore, isWindowsStore } from "../utils"; +import { + allowBrowserintegrationOverride, + isAppImage, + isDev, + isFlatpak, + isMacAppStore, + isSnapStore, + isWindowsStore, +} from "../utils"; import { ClipboardWriteMessage } from "./types/clipboard"; @@ -140,6 +148,7 @@ export default { isFlatpak: isFlatpak(), isSnapStore: isSnapStore(), isAppImage: isAppImage(), + allowBrowserintegrationOverride: allowBrowserintegrationOverride(), reloadProcess: () => ipcRenderer.send("reload-process"), focusWindow: () => ipcRenderer.send("window-focus"), hideWindow: () => ipcRenderer.send("window-hide"), diff --git a/apps/desktop/src/utils.ts b/apps/desktop/src/utils.ts index 98bdebb0cc..88feadd672 100644 --- a/apps/desktop/src/utils.ts +++ b/apps/desktop/src/utils.ts @@ -70,6 +70,16 @@ export function isWindowsPortable() { return isWindows() && process.env.PORTABLE_EXECUTABLE_DIR != null; } +/** + * We block the browser integration on some unsupported platforms, which also + * blocks partially supported platforms (mac .dmg in dev builds) / prevents + * experimenting with the feature for QA. So this env var allows overriding + * the block. + */ +export function allowBrowserintegrationOverride() { + return process.env.ALLOW_BROWSER_INTEGRATION_OVERRIDE === "true"; +} + /** * Sanitize user agent so external resources used by the app can't built data on our users. */ From ba4d7ea7994a219ad735d927869dfd953af0f97d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 11:57:58 +0100 Subject: [PATCH 016/112] [deps] Platform: Update electron-log to v5.2.4 (#12300) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Daniel García --- 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 1abea1dbcf..0387e8855b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -134,7 +134,7 @@ "css-loader": "7.1.2", "electron": "32.1.1", "electron-builder": "24.13.3", - "electron-log": "5.2.2", + "electron-log": "5.2.4", "electron-reload": "2.0.0-alpha.1", "electron-store": "8.2.0", "electron-updater": "6.3.9", @@ -15783,9 +15783,9 @@ } }, "node_modules/electron-log": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-5.2.2.tgz", - "integrity": "sha512-fgvx6srjIHDowJD8WAAjoAXmiTyOz6JnGQoxOtk1mXw7o4S+HutuPHLCsk24xTXqWZgy4uO63NbedG+oEvldLw==", + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-5.2.4.tgz", + "integrity": "sha512-iX12WXc5XAaKeHg2QpiFjVwL+S1NVHPFd3V5RXtCmKhpAzXsVQnR3UEc0LovM6p6NkUQxDWnkdkaam9FNUVmCA==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index 68017a9236..0d4da63e70 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,7 @@ "css-loader": "7.1.2", "electron": "32.1.1", "electron-builder": "24.13.3", - "electron-log": "5.2.2", + "electron-log": "5.2.4", "electron-reload": "2.0.0-alpha.1", "electron-store": "8.2.0", "electron-updater": "6.3.9", From e8976556d8e70bdfbdaf70af3d41f1ddf7fd5f0c Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Mon, 9 Dec 2024 06:40:58 -0800 Subject: [PATCH 017/112] Fix missing title for ssh key dialog (#12317) --- .../vault-item-dialog/vault-item-dialog.component.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts index 2f5b014fcb..ecceaa6267 100644 --- a/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts +++ b/apps/web/src/app/vault/components/vault-item-dialog/vault-item-dialog.component.ts @@ -428,6 +428,9 @@ export class VaultItemDialogComponent implements OnInit, OnDestroy { case CipherType.SecureNote: this.title = this.i18nService.t(partOne, this.i18nService.t("note").toLowerCase()); break; + case CipherType.SshKey: + this.title = this.i18nService.t(partOne, this.i18nService.t("typeSshKey").toLowerCase()); + break; } } From 8c446b4720b94a6a103b9bef155f5370730dd7a5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 15:42:42 +0100 Subject: [PATCH 018/112] [deps] Platform: Update Rust crate anyhow to v1.0.94 (#12296) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Daniel García --- apps/desktop/desktop_native/Cargo.lock | 6 +++--- apps/desktop/desktop_native/core/Cargo.toml | 2 +- apps/desktop/desktop_native/napi/Cargo.toml | 2 +- apps/desktop/desktop_native/objc/Cargo.toml | 2 +- apps/desktop/desktop_native/proxy/Cargo.toml | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 148e56fcb0..1cf8b24c26 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -70,9 +70,9 @@ checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anyhow" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" +checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" [[package]] name = "arboard" diff --git a/apps/desktop/desktop_native/core/Cargo.toml b/apps/desktop/desktop_native/core/Cargo.toml index 4ace1c13e0..adfdd818a1 100644 --- a/apps/desktop/desktop_native/core/Cargo.toml +++ b/apps/desktop/desktop_native/core/Cargo.toml @@ -21,7 +21,7 @@ sys = [ [dependencies] aes = "=0.8.4" -anyhow = "=1.0.93" +anyhow = "=1.0.94" arboard = { version = "=3.4.1", default-features = false, features = [ "wayland-data-control", ] } diff --git a/apps/desktop/desktop_native/napi/Cargo.toml b/apps/desktop/desktop_native/napi/Cargo.toml index 6efd662b35..08664eb6a5 100644 --- a/apps/desktop/desktop_native/napi/Cargo.toml +++ b/apps/desktop/desktop_native/napi/Cargo.toml @@ -16,7 +16,7 @@ manual_test = [] [dependencies] base64 = "=0.22.1" hex = "=0.4.3" -anyhow = "=1.0.93" +anyhow = "=1.0.94" desktop_core = { path = "../core" } napi = { version = "=2.16.13", features = ["async"] } napi-derive = "=2.16.13" diff --git a/apps/desktop/desktop_native/objc/Cargo.toml b/apps/desktop/desktop_native/objc/Cargo.toml index 4b883ce6fa..cfcc92bef7 100644 --- a/apps/desktop/desktop_native/objc/Cargo.toml +++ b/apps/desktop/desktop_native/objc/Cargo.toml @@ -9,7 +9,7 @@ publish = false default = [] [dependencies] -anyhow = "=1.0.93" +anyhow = "=1.0.94" thiserror = "=1.0.69" tokio = "1.39.1" diff --git a/apps/desktop/desktop_native/proxy/Cargo.toml b/apps/desktop/desktop_native/proxy/Cargo.toml index 0511f583c1..06e587b442 100644 --- a/apps/desktop/desktop_native/proxy/Cargo.toml +++ b/apps/desktop/desktop_native/proxy/Cargo.toml @@ -7,7 +7,7 @@ version = "0.0.0" publish = false [dependencies] -anyhow = "=1.0.93" +anyhow = "=1.0.94" desktop_core = { path = "../core", default-features = false } futures = "=0.3.31" log = "=0.4.22" From b5ce2167eb8f5520d2b02b5e8c6b259403251af3 Mon Sep 17 00:00:00 2001 From: Nick Krantz <125900171+nick-livefront@users.noreply.github.com> Date: Mon, 9 Dec 2024 09:04:16 -0600 Subject: [PATCH 019/112] update styles of the password history to use bitLink & button (#12246) --- .../item-history/item-history-v2.component.html | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/libs/vault/src/cipher-view/item-history/item-history-v2.component.html b/libs/vault/src/cipher-view/item-history/item-history-v2.component.html index 35b0428d12..a207b8fa1c 100644 --- a/libs/vault/src/cipher-view/item-history/item-history-v2.component.html +++ b/libs/vault/src/cipher-view/item-history/item-history-v2.component.html @@ -25,13 +25,14 @@ {{ "datePasswordUpdated" | i18n }}: {{ cipher.passwordRevisionDisplayDate | date: "medium" }}
+ {{ "claimedDomainsDesc" | i18n }} + + + +
{{ "noClientsInList" | i18n }}
{{ "noClients" | i18n }}
@@ -15,8 +15,11 @@
{{ "revokeMembersWarning" | i18n }}
- {{ "restoreMembersInstructions" | i18n }} -
- {{ "removeMembersWithoutMasterPasswordWarning" | i18n }} -
{{ "revokeUsersWarning" | i18n }}
+
{{ "claimedDomainsDesc" | i18n }} Date: Tue, 17 Dec 2024 16:54:04 +0100 Subject: [PATCH 096/112] Remove v1 tools owned settings pages and extension refresh conditional routing (#12350) Co-authored-by: Daniel James Smith --- apps/browser/src/_locales/en/messages.json | 3 - apps/browser/src/popup/app-routing.module.ts | 18 ++-- apps/browser/src/popup/app.module.ts | 2 - .../about-page/about-page.component.html | 63 ------------ .../about-page/about-page.component.ts | 84 ---------------- .../more-from-bitwarden-page.component.html | 76 --------------- .../more-from-bitwarden-page.component.ts | 97 ------------------- .../popup/settings/settings.component.html | 63 ------------ .../popup/settings/settings.component.ts | 9 -- 9 files changed, 9 insertions(+), 406 deletions(-) delete mode 100644 apps/browser/src/tools/popup/settings/about-page/about-page.component.html delete mode 100644 apps/browser/src/tools/popup/settings/about-page/about-page.component.ts delete mode 100644 apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page.component.html delete mode 100644 apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page.component.ts delete mode 100644 apps/browser/src/tools/popup/settings/settings.component.html delete mode 100644 apps/browser/src/tools/popup/settings/settings.component.ts diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index 8eb2aecaf6..c2e9ef60d8 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -648,9 +648,6 @@ "rateExtension": { "message": "Rate the extension" }, - "rateExtensionDesc": { - "message": "Please consider helping us out with a good review!" - }, "browserNotSupportClipboard": { "message": "Your web browser does not support easy clipboard copying. Copy it manually instead." }, diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index f349ada137..85ae861c9d 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -91,13 +91,10 @@ import { SendAddEditComponent as SendAddEditV2Component } from "../tools/popup/s import { SendCreatedComponent } from "../tools/popup/send-v2/send-created/send-created.component"; import { SendV2Component } from "../tools/popup/send-v2/send-v2.component"; import { AboutPageV2Component } from "../tools/popup/settings/about-page/about-page-v2.component"; -import { AboutPageComponent } from "../tools/popup/settings/about-page/about-page.component"; import { MoreFromBitwardenPageV2Component } from "../tools/popup/settings/about-page/more-from-bitwarden-page-v2.component"; -import { MoreFromBitwardenPageComponent } from "../tools/popup/settings/about-page/more-from-bitwarden-page.component"; import { ExportBrowserV2Component } from "../tools/popup/settings/export/export-browser-v2.component"; import { ImportBrowserV2Component } from "../tools/popup/settings/import/import-browser-v2.component"; import { SettingsV2Component } from "../tools/popup/settings/settings-v2.component"; -import { SettingsComponent } from "../tools/popup/settings/settings.component"; import { clearVaultStateGuard } from "../vault/guards/clear-vault-state.guard"; import { AddEditComponent } from "../vault/popup/components/vault/add-edit.component"; import { AttachmentsComponent } from "../vault/popup/components/vault/attachments.component"; @@ -702,16 +699,18 @@ const routes: Routes = [ canActivate: [canAccessFeature(FeatureFlag.ExtensionRefresh, true, "/")], data: { elevation: 1 } satisfies RouteDataProperties, }, - ...extensionRefreshSwap(AboutPageComponent, AboutPageV2Component, { + { path: "about", + component: AboutPageV2Component, canActivate: [authGuard], data: { elevation: 1 } satisfies RouteDataProperties, - }), - ...extensionRefreshSwap(MoreFromBitwardenPageComponent, MoreFromBitwardenPageV2Component, { + }, + { path: "more-from-bitwarden", + component: MoreFromBitwardenPageV2Component, canActivate: [authGuard], data: { elevation: 2 } satisfies RouteDataProperties, - }), + }, ...extensionRefreshSwap(TabsComponent, TabsV2Component, { path: "tabs", data: { elevation: 0 } satisfies RouteDataProperties, @@ -740,11 +739,12 @@ const routes: Routes = [ canActivate: [authGuard], data: { elevation: 0 } satisfies RouteDataProperties, }), - ...extensionRefreshSwap(SettingsComponent, SettingsV2Component, { + { path: "settings", + component: SettingsV2Component, canActivate: [authGuard], data: { elevation: 0 } satisfies RouteDataProperties, - }), + }, ...extensionRefreshSwap(SendGroupingsComponent, SendV2Component, { path: "send", canActivate: [authGuard], diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts index 760b43a879..76bd06565c 100644 --- a/apps/browser/src/popup/app.module.ts +++ b/apps/browser/src/popup/app.module.ts @@ -63,7 +63,6 @@ import { SendListComponent } from "../tools/popup/send/components/send-list.comp import { SendAddEditComponent } from "../tools/popup/send/send-add-edit.component"; import { SendGroupingsComponent } from "../tools/popup/send/send-groupings.component"; import { SendTypeComponent } from "../tools/popup/send/send-type.component"; -import { SettingsComponent } from "../tools/popup/settings/settings.component"; import { ActionButtonsComponent } from "../vault/popup/components/action-buttons.component"; import { CipherRowComponent } from "../vault/popup/components/cipher-row.component"; import { AddEditCustomFieldsComponent } from "../vault/popup/components/vault/add-edit-custom-fields.component"; @@ -174,7 +173,6 @@ import "../platform/popup/locales"; SendListComponent, SendTypeComponent, SetPasswordComponent, - SettingsComponent, VaultSettingsComponent, ShareComponent, SsoComponentV1, diff --git a/apps/browser/src/tools/popup/settings/about-page/about-page.component.html b/apps/browser/src/tools/popup/settings/about-page/about-page.component.html deleted file mode 100644 index 7537c75bd9..0000000000 --- a/apps/browser/src/tools/popup/settings/about-page/about-page.component.html +++ /dev/null @@ -1,63 +0,0 @@ - - - - - {{ "back" | i18n }} - - - - {{ "about" | i18n }} - - - - - - - - - - {{ "aboutBitwarden" | i18n }} - - - {{ "helpCenter" | i18n }} - - - - {{ "bitWebVaultApp" | i18n }} - - - - {{ "moreFromBitwarden" | i18n }} - - - - {{ "rateExtension" | i18n }} - - - - - diff --git a/apps/browser/src/tools/popup/settings/about-page/about-page.component.ts b/apps/browser/src/tools/popup/settings/about-page/about-page.component.ts deleted file mode 100644 index 7c3e87a92f..0000000000 --- a/apps/browser/src/tools/popup/settings/about-page/about-page.component.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { Component } from "@angular/core"; -import { RouterModule } from "@angular/router"; -import { firstValueFrom } from "rxjs"; - -import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { DeviceType } from "@bitwarden/common/enums"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { DialogService } from "@bitwarden/components"; - -import { BrowserApi } from "../../../../platform/browser/browser-api"; -import { PopOutComponent } from "../../../../platform/popup/components/pop-out.component"; -import { AboutDialogComponent } from "../about-dialog/about-dialog.component"; - -const RateUrls = { - [DeviceType.ChromeExtension]: - "https://chromewebstore.google.com/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb/reviews", - [DeviceType.FirefoxExtension]: - "https://addons.mozilla.org/en-US/firefox/addon/bitwarden-password-manager/#reviews", - [DeviceType.OperaExtension]: - "https://addons.opera.com/en/extensions/details/bitwarden-free-password-manager/#feedback-container", - [DeviceType.EdgeExtension]: - "https://microsoftedge.microsoft.com/addons/detail/jbkfoedolllekgbhcbcoahefnbanhhlh", - [DeviceType.VivaldiExtension]: - "https://chromewebstore.google.com/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb/reviews", - [DeviceType.SafariExtension]: "https://apps.apple.com/app/bitwarden/id1352778147", -}; - -@Component({ - templateUrl: "about-page.component.html", - standalone: true, - imports: [CommonModule, JslibModule, RouterModule, PopOutComponent], -}) -export class AboutPageComponent { - constructor( - private dialogService: DialogService, - private environmentService: EnvironmentService, - private platformUtilsService: PlatformUtilsService, - ) {} - - about() { - this.dialogService.open(AboutDialogComponent); - } - - async launchHelp() { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "continueToHelpCenter" }, - content: { key: "continueToHelpCenterDesc" }, - type: "info", - acceptButtonText: { key: "continue" }, - }); - if (confirmed) { - await BrowserApi.createNewTab("https://bitwarden.com/help/"); - } - } - - async openWebVault() { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "continueToWebApp" }, - content: { key: "continueToWebAppDesc" }, - type: "info", - acceptButtonText: { key: "continue" }, - }); - if (confirmed) { - const env = await firstValueFrom(this.environmentService.environment$); - const url = env.getWebVaultUrl(); - await BrowserApi.createNewTab(url); - } - } - - async rate() { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "continueToBrowserExtensionStore" }, - content: { key: "continueToBrowserExtensionStoreDesc" }, - type: "info", - acceptButtonText: { key: "continue" }, - }); - if (confirmed) { - const deviceType = this.platformUtilsService.getDevice(); - await BrowserApi.createNewTab((RateUrls as any)[deviceType]); - } - } -} diff --git a/apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page.component.html b/apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page.component.html deleted file mode 100644 index 8e7b349536..0000000000 --- a/apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page.component.html +++ /dev/null @@ -1,76 +0,0 @@ - - - - - {{ "back" | i18n }} - - - - {{ "moreFromBitwarden" | i18n }} - - - - - - - - - - - {{ "premiumMembership" | i18n }} - - - - - {{ "freeBitwardenFamilies" | i18n }} - - - - {{ "bitwardenForBusiness" | i18n }} - - - - {{ "bitwardenAuthenticator" | i18n }} - - - - {{ "bitwardenSecretsManager" | i18n }} - - - - {{ "passwordlessDotDev" | i18n }} - - - - - diff --git a/apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page.component.ts b/apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page.component.ts deleted file mode 100644 index 1f26d40b34..0000000000 --- a/apps/browser/src/tools/popup/settings/about-page/more-from-bitwarden-page.component.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { CommonModule } from "@angular/common"; -import { Component } from "@angular/core"; -import { RouterModule } from "@angular/router"; -import { Observable, firstValueFrom } from "rxjs"; - -import { JslibModule } from "@bitwarden/angular/jslib.module"; -import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { DialogService } from "@bitwarden/components"; - -import { BrowserApi } from "../../../../platform/browser/browser-api"; -import { PopOutComponent } from "../../../../platform/popup/components/pop-out.component"; -import { FamiliesPolicyService } from "../../../../services/families-policy.service"; - -@Component({ - templateUrl: "more-from-bitwarden-page.component.html", - standalone: true, - imports: [CommonModule, JslibModule, RouterModule, PopOutComponent], -}) -export class MoreFromBitwardenPageComponent { - canAccessPremium$: Observable; - protected isFreeFamilyPolicyEnabled$: Observable; - protected hasSingleEnterpriseOrg$: Observable; - - constructor( - private dialogService: DialogService, - private billingAccountProfileStateService: BillingAccountProfileStateService, - private environmentService: EnvironmentService, - private familiesPolicyService: FamiliesPolicyService, - ) { - this.canAccessPremium$ = billingAccountProfileStateService.hasPremiumFromAnySource$; - this.hasSingleEnterpriseOrg$ = this.familiesPolicyService.hasSingleEnterpriseOrg$(); - this.isFreeFamilyPolicyEnabled$ = this.familiesPolicyService.isFreeFamilyPolicyEnabled$(); - } - - async openFreeBitwardenFamiliesPage() { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "continueToWebApp" }, - content: { key: "freeBitwardenFamiliesPageDesc" }, - type: "info", - acceptButtonText: { key: "continue" }, - }); - if (confirmed) { - const env = await firstValueFrom(this.environmentService.environment$); - const url = env.getWebVaultUrl(); - await BrowserApi.createNewTab(url + "/#/settings/sponsored-families"); - } - } - - async openBitwardenForBusinessPage() { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "continueToBitwardenDotCom" }, - content: { key: "bitwardenForBusinessPageDesc" }, - type: "info", - acceptButtonText: { key: "continue" }, - }); - if (confirmed) { - await BrowserApi.createNewTab("https://bitwarden.com/products/business/"); - } - } - - async openAuthenticatorPage() { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "continueToBitwardenDotCom" }, - content: { key: "continueToAuthenticatorPageDesc" }, - type: "info", - acceptButtonText: { key: "continue" }, - }); - if (confirmed) { - await BrowserApi.createNewTab("https://bitwarden.com/products/authenticator"); - } - } - - async openSecretsManagerPage() { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "continueToBitwardenDotCom" }, - content: { key: "continueToSecretsManagerPageDesc" }, - type: "info", - acceptButtonText: { key: "continue" }, - }); - if (confirmed) { - await BrowserApi.createNewTab("https://bitwarden.com/products/secrets-manager"); - } - } - - async openPasswordlessDotDevPage() { - const confirmed = await this.dialogService.openSimpleDialog({ - title: { key: "continueToBitwardenDotCom" }, - content: { key: "continueToPasswordlessDotDevPageDesc" }, - type: "info", - acceptButtonText: { key: "continue" }, - }); - if (confirmed) { - await BrowserApi.createNewTab("https://bitwarden.com/products/passwordless"); - } - } -} diff --git a/apps/browser/src/tools/popup/settings/settings.component.html b/apps/browser/src/tools/popup/settings/settings.component.html deleted file mode 100644 index c547229653..0000000000 --- a/apps/browser/src/tools/popup/settings/settings.component.html +++ /dev/null @@ -1,63 +0,0 @@ - - - - {{ "settings" | i18n }} - - - - - - - - - - {{ "accountSecurity" | i18n }} - - - - {{ "autofill" | i18n }} - - - - {{ "notifications" | i18n }} - - - - {{ "vault" | i18n }} - - - - {{ "appearance" | i18n }} - - - - {{ "about" | i18n }} - - - - - diff --git a/apps/browser/src/tools/popup/settings/settings.component.ts b/apps/browser/src/tools/popup/settings/settings.component.ts deleted file mode 100644 index 973efc7203..0000000000 --- a/apps/browser/src/tools/popup/settings/settings.component.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Component } from "@angular/core"; - -@Component({ - selector: "tools-settings", - templateUrl: "settings.component.html", -}) -export class SettingsComponent { - constructor() {} -} From c09f65afceb725203ec206e0208134cc896d2414 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rui=20Tom=C3=A9?= <108268980+r-tome@users.noreply.github.com> Date: Tue, 17 Dec 2024 16:00:36 +0000 Subject: [PATCH 097/112] [PM-15903] Fix icon alignment for bulk remove, bulk delete, and individual delete buttons in the members component (#12437) --- .../organizations/members/members.component.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/web/src/app/admin-console/organizations/members/members.component.html b/apps/web/src/app/admin-console/organizations/members/members.component.html index 94ee97edc1..52315d3017 100644 --- a/apps/web/src/app/admin-console/organizations/members/members.component.html +++ b/apps/web/src/app/admin-console/organizations/members/members.component.html @@ -148,7 +148,7 @@ *ngIf="showBulkRemoveUsers" > - + {{ "remove" | i18n }} @@ -159,7 +159,7 @@ *ngIf="showBulkDeleteUsers" > - + {{ "delete" | i18n }} @@ -358,7 +358,7 @@ (click)="deleteUser(u)" > - + {{ "delete" | i18n }} From 1d874b447e61bb15825df3e9aadf5558a7ede9de Mon Sep 17 00:00:00 2001 From: Github Actions Date: Tue, 17 Dec 2024 16:25:12 +0000 Subject: [PATCH 098/112] Bumped Desktop client to 2024.12.2 --- 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 101e968ad6..99953603e4 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": "2024.12.1", + "version": "2024.12.2", "keywords": [ "bitwarden", "password", diff --git a/apps/desktop/src/package-lock.json b/apps/desktop/src/package-lock.json index 201f563db2..7abf1d9392 100644 --- a/apps/desktop/src/package-lock.json +++ b/apps/desktop/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "@bitwarden/desktop", - "version": "2024.12.1", + "version": "2024.12.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@bitwarden/desktop", - "version": "2024.12.1", + "version": "2024.12.2", "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 29ee5dc47e..91d4f4fca9 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": "2024.12.1", + "version": "2024.12.2", "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 1ffc977a08..997cad9551 100644 --- a/package-lock.json +++ b/package-lock.json @@ -230,7 +230,7 @@ }, "apps/desktop": { "name": "@bitwarden/desktop", - "version": "2024.12.1", + "version": "2024.12.2", "hasInstallScript": true, "license": "GPL-3.0" }, From d1fe72a4ab37bd87836e8a2ad03c6c66999c03de Mon Sep 17 00:00:00 2001 From: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Date: Tue, 17 Dec 2024 20:34:30 +0100 Subject: [PATCH 099/112] Fix the maxlength org name bug (#12397) --- apps/web/src/locales/en/messages.json | 3 +++ .../providers/clients/create-client-dialog.component.html | 8 ++++++++ .../providers/clients/create-client-dialog.component.ts | 2 +- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index abc3aa3a1d..36143682fa 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -9969,5 +9969,8 @@ }, "domainClaimed": { "message": "Domain claimed" + }, + "organizationNameMaxLength": { + "message": "Organization name cannot exceed 50 characters." } } diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.html b/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.html index a08f5710f1..78f2cb41be 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.html +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.html @@ -43,6 +43,14 @@ {{ "organizationName" | i18n }} + + {{ "organizationNameMaxLength" | i18n }} + diff --git a/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.ts b/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.ts index 18910491a0..2a27b1b32f 100644 --- a/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.ts +++ b/bitwarden_license/bit-web/src/app/billing/providers/clients/create-client-dialog.component.ts @@ -47,7 +47,7 @@ export class CreateClientDialogComponent implements OnInit { protected discountPercentage: number; protected formGroup = new FormGroup({ clientOwnerEmail: new FormControl("", [Validators.required, Validators.email]), - organizationName: new FormControl("", [Validators.required]), + organizationName: new FormControl("", [Validators.required, Validators.maxLength(50)]), seats: new FormControl(null, [Validators.required, Validators.min(1)]), }); protected loading = true; From e2e9a7c345e0594efbf258f17f76d9dd6ecf83ae Mon Sep 17 00:00:00 2001 From: Daniel James Smith <2670567+djsmith85@users.noreply.github.com> Date: Tue, 17 Dec 2024 20:51:58 +0100 Subject: [PATCH 100/112] [PM-15483] PasswordXP-CSV-Importer: Add support for German and Dutch headers (#12216) * Add tests to verify importing German and Dutch headers work * Add method to translate the headers from (German/Dutch into English) while the CSV data is being parsed * Report "importFormatError" when header translation did not work, instead of a generic undefined error (startsWith) * Move passwordxp-csv-importer into a dedicated folder * Introduce files with the language header translations --------- Co-authored-by: Daniel James Smith --- .../spec/passwordxp-csv-importer.spec.ts | 80 ++++++++++++------- .../test-data/passwordxp-csv/dutch-headers.ts | 7 ++ .../passwordxp-csv/german-headers.ts | 7 ++ libs/importer/src/importers/index.ts | 2 +- .../importers/passsordxp/dutch-csv-headers.ts | 10 +++ .../passsordxp/german-csv-headers.ts | 11 +++ .../passwordxp-csv-importer.ts | 39 +++++++-- 7 files changed, 117 insertions(+), 39 deletions(-) create mode 100644 libs/importer/spec/test-data/passwordxp-csv/dutch-headers.ts create mode 100644 libs/importer/spec/test-data/passwordxp-csv/german-headers.ts create mode 100644 libs/importer/src/importers/passsordxp/dutch-csv-headers.ts create mode 100644 libs/importer/src/importers/passsordxp/german-csv-headers.ts rename libs/importer/src/importers/{ => passsordxp}/passwordxp-csv-importer.ts (68%) diff --git a/libs/importer/spec/passwordxp-csv-importer.spec.ts b/libs/importer/spec/passwordxp-csv-importer.spec.ts index f707b1138c..fda323450c 100644 --- a/libs/importer/spec/passwordxp-csv-importer.spec.ts +++ b/libs/importer/spec/passwordxp-csv-importer.spec.ts @@ -3,10 +3,46 @@ import { CipherType } from "@bitwarden/common/vault/enums"; import { PasswordXPCsvImporter } from "../src/importers"; import { ImportResult } from "../src/models/import-result"; +import { dutchHeaders } from "./test-data/passwordxp-csv/dutch-headers"; +import { germanHeaders } from "./test-data/passwordxp-csv/german-headers"; import { noFolder } from "./test-data/passwordxp-csv/no-folder.csv"; import { withFolders } from "./test-data/passwordxp-csv/passwordxp-with-folders.csv"; import { withoutFolders } from "./test-data/passwordxp-csv/passwordxp-without-folders.csv"; +async function importLoginWithCustomFields(importer: PasswordXPCsvImporter, csvData: string) { + const result: ImportResult = await importer.parse(csvData); + expect(result.success).toBe(true); + + const cipher = result.ciphers.shift(); + expect(cipher.type).toBe(CipherType.Login); + expect(cipher.name).toBe("Title2"); + expect(cipher.notes).toBe("Test Notes"); + expect(cipher.login.username).toBe("Username2"); + expect(cipher.login.password).toBe("12345678"); + expect(cipher.login.uris[0].uri).toBe("http://URL2.com"); + + expect(cipher.fields.length).toBe(5); + let field = cipher.fields.shift(); + expect(field.name).toBe("Account"); + expect(field.value).toBe("Account2"); + + field = cipher.fields.shift(); + expect(field.name).toBe("Modified"); + expect(field.value).toBe("27-3-2024 08:11:21"); + + field = cipher.fields.shift(); + expect(field.name).toBe("Created"); + expect(field.value).toBe("27-3-2024 08:11:21"); + + field = cipher.fields.shift(); + expect(field.name).toBe("Expire on"); + expect(field.value).toBe("27-5-2024 08:11:21"); + + field = cipher.fields.shift(); + expect(field.name).toBe("Modified by"); + expect(field.value).toBe("someone"); +} + describe("PasswordXPCsvImporter", () => { let importer: PasswordXPCsvImporter; @@ -20,6 +56,12 @@ describe("PasswordXPCsvImporter", () => { expect(result.success).toBe(false); }); + it("should return success false if CSV headers did not get translated", async () => { + const data = germanHeaders.replace("Titel;", "UnknownTitle;"); + const result: ImportResult = await importer.parse(data); + expect(result.success).toBe(false); + }); + it("should skip rows starting with >>>", async () => { const result: ImportResult = await importer.parse(noFolder); expect(result.success).toBe(true); @@ -61,38 +103,16 @@ describe("PasswordXPCsvImporter", () => { expect(cipher.login.uris[0].uri).toBe("http://test"); }); - it("should parse CSV data and import unmapped columns as custom fields", async () => { - const result: ImportResult = await importer.parse(withoutFolders); - expect(result.success).toBe(true); + it("should parse CSV data with English headers and import unmapped columns as custom fields", async () => { + await importLoginWithCustomFields(importer, withoutFolders); + }); - const cipher = result.ciphers.shift(); - expect(cipher.type).toBe(CipherType.Login); - expect(cipher.name).toBe("Title2"); - expect(cipher.notes).toBe("Test Notes"); - expect(cipher.login.username).toBe("Username2"); - expect(cipher.login.password).toBe("12345678"); - expect(cipher.login.uris[0].uri).toBe("http://URL2.com"); + it("should parse CSV data with German headers and import unmapped columns as custom fields", async () => { + await importLoginWithCustomFields(importer, germanHeaders); + }); - expect(cipher.fields.length).toBe(5); - let field = cipher.fields.shift(); - expect(field.name).toBe("Account"); - expect(field.value).toBe("Account2"); - - field = cipher.fields.shift(); - expect(field.name).toBe("Modified"); - expect(field.value).toBe("27-3-2024 08:11:21"); - - field = cipher.fields.shift(); - expect(field.name).toBe("Created"); - expect(field.value).toBe("27-3-2024 08:11:21"); - - field = cipher.fields.shift(); - expect(field.name).toBe("Expire on"); - expect(field.value).toBe("27-5-2024 08:11:21"); - - field = cipher.fields.shift(); - expect(field.name).toBe("Modified by"); - expect(field.value).toBe("someone"); + it("should parse CSV data with Dutch headers and import unmapped columns as custom fields", async () => { + await importLoginWithCustomFields(importer, dutchHeaders); }); it("should parse CSV data with folders and assign items to them", async () => { diff --git a/libs/importer/spec/test-data/passwordxp-csv/dutch-headers.ts b/libs/importer/spec/test-data/passwordxp-csv/dutch-headers.ts new file mode 100644 index 0000000000..9cab04f1e6 --- /dev/null +++ b/libs/importer/spec/test-data/passwordxp-csv/dutch-headers.ts @@ -0,0 +1,7 @@ +export const dutchHeaders = `Titel;Gebruikersnaam;Account;URL;Wachtwoord;Gewijzigd;Gemaakt;Verloopt op;Beschrijving;Gewijzigd door +>>> +Title2;Username2;Account2;http://URL2.com;12345678;27-3-2024 08:11:21;27-3-2024 08:11:21;27-5-2024 08:11:21;Test Notes;someone +Title Test 1;Username1;Account1;http://URL1.com;Password1;27-3-2024 08:10:52;27-3-2024 08:10:52;;Test Notes 2; +Certificate 1;;;;;27-3-2024 10:22:39;27-3-2024 10:22:39;;Test Notes Certicate 1; +test;testtest;;http://test;test;27-3-2024 12:36:59;27-3-2024 12:36:59;;Test Notes 3; +`; diff --git a/libs/importer/spec/test-data/passwordxp-csv/german-headers.ts b/libs/importer/spec/test-data/passwordxp-csv/german-headers.ts new file mode 100644 index 0000000000..a6ac21c76d --- /dev/null +++ b/libs/importer/spec/test-data/passwordxp-csv/german-headers.ts @@ -0,0 +1,7 @@ +export const germanHeaders = `Titel;Benutzername;Konto;URL;Passwort;Geändert am;Erstellt am;Läuft ab am;Beschreibung;Geändert von +>>> +Title2;Username2;Account2;http://URL2.com;12345678;27-3-2024 08:11:21;27-3-2024 08:11:21;27-5-2024 08:11:21;Test Notes;someone +Title Test 1;Username1;Account1;http://URL1.com;Password1;27-3-2024 08:10:52;27-3-2024 08:10:52;;Test Notes 2; +Certificate 1;;;;;27-3-2024 10:22:39;27-3-2024 10:22:39;;Test Notes Certicate 1; +test;testtest;;http://test;test;27-3-2024 12:36:59;27-3-2024 12:36:59;;Test Notes 3; +`; diff --git a/libs/importer/src/importers/index.ts b/libs/importer/src/importers/index.ts index 19b22cfa80..1ba3a0d9eb 100644 --- a/libs/importer/src/importers/index.ts +++ b/libs/importer/src/importers/index.ts @@ -45,7 +45,7 @@ export { PasswordBossJsonImporter } from "./passwordboss-json-importer"; export { PasswordDragonXmlImporter } from "./passworddragon-xml-importer"; export { PasswordSafeXmlImporter } from "./passwordsafe-xml-importer"; export { PasswordWalletTxtImporter } from "./passwordwallet-txt-importer"; -export { PasswordXPCsvImporter } from "./passwordxp-csv-importer"; +export { PasswordXPCsvImporter } from "./passsordxp/passwordxp-csv-importer"; export { ProtonPassJsonImporter } from "./protonpass/protonpass-json-importer"; export { PsonoJsonImporter } from "./psono/psono-json-importer"; export { RememBearCsvImporter } from "./remembear-csv-importer"; diff --git a/libs/importer/src/importers/passsordxp/dutch-csv-headers.ts b/libs/importer/src/importers/passsordxp/dutch-csv-headers.ts new file mode 100644 index 0000000000..7f9c219de5 --- /dev/null +++ b/libs/importer/src/importers/passsordxp/dutch-csv-headers.ts @@ -0,0 +1,10 @@ +export const dutchHeaderTranslations: { [key: string]: string } = { + Titel: "Title", + Gebruikersnaam: "Username", + Wachtwoord: "Password", + Gewijzigd: "Modified", + Gemaakt: "Created", + "Verloopt op": "Expire on", + Beschrijving: "Description", + "Gewijzigd door": "Modified by", +}; diff --git a/libs/importer/src/importers/passsordxp/german-csv-headers.ts b/libs/importer/src/importers/passsordxp/german-csv-headers.ts new file mode 100644 index 0000000000..584ad0badc --- /dev/null +++ b/libs/importer/src/importers/passsordxp/german-csv-headers.ts @@ -0,0 +1,11 @@ +export const germanHeaderTranslations: { [key: string]: string } = { + Titel: "Title", + Benutzername: "Username", + Konto: "Account", + Passwort: "Password", + "Geändert am": "Modified", + "Erstellt am": "Created", + "Läuft ab am": "Expire on", + Beschreibung: "Description", + "Geändert von": "Modified by", +}; diff --git a/libs/importer/src/importers/passwordxp-csv-importer.ts b/libs/importer/src/importers/passsordxp/passwordxp-csv-importer.ts similarity index 68% rename from libs/importer/src/importers/passwordxp-csv-importer.ts rename to libs/importer/src/importers/passsordxp/passwordxp-csv-importer.ts index 461432e98d..226a284ec9 100644 --- a/libs/importer/src/importers/passwordxp-csv-importer.ts +++ b/libs/importer/src/importers/passsordxp/passwordxp-csv-importer.ts @@ -1,12 +1,28 @@ import { Utils } from "@bitwarden/common/platform/misc/utils"; import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view"; -import { ImportResult } from "../models/import-result"; - -import { BaseImporter } from "./base-importer"; -import { Importer } from "./importer"; +import { ImportResult } from "../../models/import-result"; +import { BaseImporter } from "../base-importer"; +import { Importer } from "../importer"; const _mappedColumns = new Set(["Title", "Username", "URL", "Password", "Description"]); +import { dutchHeaderTranslations } from "./dutch-csv-headers"; +import { germanHeaderTranslations } from "./german-csv-headers"; + +/* Translates the headers from non-English to English + * This is necessary because the parser only maps English headers to ciphers + * Currently only supports German and Dutch translations + */ +function translateIntoEnglishHeaders(header: string): string { + const translations: { [key: string]: string } = { + // The header column 'User name' is parsed by the parser, but cannot be used as a variable. This converts it to a valid variable name, prior to parsing. + "User name": "Username", + ...germanHeaderTranslations, + ...dutchHeaderTranslations, + }; + + return translations[header] || header; +} /** * PasswordXP CSV importer @@ -17,15 +33,22 @@ export class PasswordXPCsvImporter extends BaseImporter implements Importer { * @param data */ parse(data: string): Promise { - // The header column 'User name' is parsed by the parser, but cannot be used as a variable. This converts it to a valid variable name, prior to parsing. - data = data.replace(";User name;", ";Username;"); - const result = new ImportResult(); - const results = this.parseCsv(data, true, { skipEmptyLines: true }); + const results = this.parseCsv(data, true, { + skipEmptyLines: true, + transformHeader: translateIntoEnglishHeaders, + }); if (results == null) { result.success = false; return Promise.resolve(result); } + + // If the first row (header check) does not contain the column "Title", then the data is invalid (no translation found) + if (!results[0].Title) { + result.success = false; + return Promise.resolve(result); + } + let currentFolderName = ""; results.forEach((row) => { // Skip rows starting with '>>>' as they indicate items following have no folder assigned to them From c3f58b2e70e3576dfa0a5634086d10a368760ce6 Mon Sep 17 00:00:00 2001 From: Evan Bassler Date: Tue, 17 Dec 2024 13:55:28 -0600 Subject: [PATCH 101/112] fix large icon (#11896) Co-authored-by: Evan Bassler Co-authored-by: Matt Bishop --- .../src/autofill/popup/settings/notifications.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/browser/src/autofill/popup/settings/notifications.component.html b/apps/browser/src/autofill/popup/settings/notifications.component.html index 86fe4923df..c6446012d0 100644 --- a/apps/browser/src/autofill/popup/settings/notifications.component.html +++ b/apps/browser/src/autofill/popup/settings/notifications.component.html @@ -50,7 +50,7 @@ {{ "excludedDomains" | i18n }} - + From ac13cf7ce6fb23e1a81ead38008e4011e5217c5b Mon Sep 17 00:00:00 2001 From: rr-bw <102181210+rr-bw@users.noreply.github.com> Date: Tue, 17 Dec 2024 12:48:37 -0800 Subject: [PATCH 102/112] feat(auth): [PM-15945] Add logout option to TDE approval page (#12445) This PR adds a logout option to the TDE approval screen. A TDE user on this page cannot use the "Back" button or click the Bitwarden logo to navigate back to `/` because the user is currently authenticated, which means that navigating to the `/` route would activate the `redirectGuard` and simply route the user back to `/login-initiated`. So we must log the user out first before routing. Feature Flags: `UnauthenticatedExtensionUIRefresh` ON --- .../login-decryption-options.component.html | 4 ++++ .../login-decryption-options.component.ts | 16 ++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.html b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.html index cb340f646f..b3d218389b 100644 --- a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.html +++ b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.html @@ -56,5 +56,9 @@ > {{ "requestAdminApproval" | i18n }} + + + {{ "logOut" | i18n }} + diff --git a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts index 070debf220..5600077c36 100644 --- a/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts +++ b/libs/auth/src/angular/login-decryption-options/login-decryption-options.component.ts @@ -30,6 +30,7 @@ import { AsyncActionsModule, ButtonModule, CheckboxModule, + DialogService, FormFieldModule, ToastService, TypographyModule, @@ -90,6 +91,7 @@ export class LoginDecryptionOptionsComponent implements OnInit { private apiService: ApiService, private destroyRef: DestroyRef, private deviceTrustService: DeviceTrustServiceAbstraction, + private dialogService: DialogService, private formBuilder: FormBuilder, private i18nService: I18nService, private keyService: KeyService, @@ -298,4 +300,18 @@ export class LoginDecryptionOptionsComponent implements OnInit { this.loginEmailService.setLoginEmail(this.email); await this.router.navigate(["/admin-approval-requested"]); } + + async logOut() { + const confirmed = await this.dialogService.openSimpleDialog({ + title: { key: "logOut" }, + content: { key: "logOutConfirmation" }, + acceptButtonText: { key: "logOut" }, + type: "warning", + }); + + const userId = (await firstValueFrom(this.accountService.activeAccount$))?.id; + if (confirmed) { + this.messagingService.send("logout", { userId: userId }); + } + } } From 5a582dfc6f7311373e7d58034192a76fade3bb0b Mon Sep 17 00:00:00 2001 From: Oscar Hinton Date: Tue, 17 Dec 2024 23:29:48 +0100 Subject: [PATCH 103/112] [CL-135] Migrate component library to standalone components (#12389) * Migrate component library to standalone components * Fix tests --- .../navigation-switcher.component.spec.ts | 11 +++----- .../password-health.component.spec.ts | 3 +-- .../src/async-actions/async-actions.module.ts | 5 +--- .../src/async-actions/bit-action.directive.ts | 1 + .../src/async-actions/bit-submit.directive.ts | 1 + .../async-actions/form-button.directive.ts | 1 + .../components/src/avatar/avatar.component.ts | 3 +++ libs/components/src/avatar/avatar.module.ts | 4 +-- .../src/badge-list/badge-list.component.ts | 6 ++++- .../src/badge-list/badge-list.module.ts | 6 +---- libs/components/src/badge/badge.directive.ts | 1 + libs/components/src/badge/badge.module.ts | 4 +-- .../src/banner/banner.component.spec.ts | 4 +-- .../components/src/banner/banner.component.ts | 6 +++++ libs/components/src/banner/banner.module.ts | 7 +---- .../src/breadcrumbs/breadcrumb.component.ts | 3 +++ .../src/breadcrumbs/breadcrumbs.component.ts | 8 ++++++ .../src/breadcrumbs/breadcrumbs.module.ts | 9 +------ .../components/src/button/button.component.ts | 3 +++ libs/components/src/button/button.module.ts | 4 +-- .../src/checkbox/checkbox.component.ts | 1 + .../src/checkbox/checkbox.module.ts | 7 +---- .../color-password.component.ts | 3 +++ .../color-password/color-password.module.ts | 4 +-- libs/components/src/dialog/dialog.module.ts | 23 ++-------------- .../src/dialog/dialog/dialog.component.ts | 15 +++++++++++ .../directives/dialog-close.directive.ts | 1 + .../dialog-title-container.directive.ts | 1 + .../simple-configurable-dialog.component.ts | 17 +++++++++++- .../simple-dialog/simple-dialog.component.ts | 10 ++++++- .../form-control/form-control.component.ts | 6 +++++ .../src/form-control/form-control.module.ts | 6 +---- .../src/form-control/hint.component.ts | 1 + .../src/form-field/error-summary.component.ts | 5 ++++ .../src/form-field/error.component.ts | 1 + .../src/form-field/form-field.component.ts | 4 +++ .../src/form-field/form-field.module.ts | 17 ++++++------ .../password-input-toggle.directive.ts | 1 + .../src/form-field/prefix.directive.ts | 1 + .../src/form-field/suffix.directive.ts | 1 + .../src/icon-button/icon-button.component.ts | 3 +++ .../src/icon-button/icon-button.module.ts | 4 +-- libs/components/src/icon/icon.component.ts | 1 + .../src/icon/icon.components.spec.ts | 2 +- libs/components/src/icon/icon.module.ts | 4 +-- libs/components/src/input/input.directive.ts | 1 + libs/components/src/input/input.module.ts | 4 +-- libs/components/src/link/link.directive.ts | 2 ++ libs/components/src/link/link.module.ts | 4 +-- .../src/menu/menu-divider.component.ts | 1 + .../src/menu/menu-item.directive.ts | 3 +++ .../src/menu/menu-trigger-for.directive.ts | 1 + libs/components/src/menu/menu.component.ts | 4 ++- libs/components/src/menu/menu.module.ts | 6 +---- libs/components/src/menu/menu.stories.ts | 16 +++--------- .../multi-select/multi-select.component.ts | 15 +++++++++-- .../src/multi-select/multi-select.module.ts | 9 +------ .../src/navigation/nav-divider.component.ts | 3 +++ .../src/navigation/nav-group.component.ts | 12 ++++++++- .../src/navigation/nav-item.component.ts | 14 ++++++++-- .../src/navigation/nav-logo.component.ts | 6 +++++ .../src/navigation/navigation.module.ts | 19 -------------- .../src/navigation/side-nav.component.ts | 8 ++++++ .../src/no-items/no-items.component.ts | 3 +++ .../src/no-items/no-items.module.ts | 6 +---- .../src/progress/progress.component.ts | 3 +++ .../src/progress/progress.module.ts | 4 +-- .../radio-button/radio-button.component.ts | 5 ++++ .../src/radio-button/radio-button.module.ts | 5 +--- .../src/radio-button/radio-group.component.ts | 4 +++ .../src/radio-button/radio-input.component.ts | 1 + .../components/src/search/search.component.ts | 11 +++++++- libs/components/src/search/search.module.ts | 7 +---- .../components/src/select/option.component.ts | 1 + .../components/src/select/select.component.ts | 13 ++++++++-- libs/components/src/select/select.module.ts | 6 +---- libs/components/src/shared/i18n.pipe.ts | 1 + libs/components/src/shared/shared.module.ts | 3 +-- libs/components/src/table/cell.directive.ts | 1 + libs/components/src/table/row.directive.ts | 1 + .../src/table/sortable.component.ts | 3 +++ .../src/table/table-scroll.component.ts | 17 ++++++++++++ libs/components/src/table/table.component.ts | 4 +++ libs/components/src/table/table.module.ts | 6 +++-- .../src/tabs/shared/tab-header.component.ts | 1 + .../shared/tab-list-container.directive.ts | 1 + .../tabs/shared/tab-list-item.directive.ts | 5 +++- .../src/tabs/tab-group/tab-body.component.ts | 4 ++- .../src/tabs/tab-group/tab-group.component.ts | 12 +++++++++ .../src/tabs/tab-group/tab-label.directive.ts | 1 + .../src/tabs/tab-group/tab.component.ts | 1 + .../tabs/tab-nav-bar/tab-link.component.ts | 4 ++- .../tabs/tab-nav-bar/tab-nav-bar.component.ts | 5 ++++ libs/components/src/tabs/tabs.module.ts | 26 +++++++------------ libs/components/src/toast/toast.module.ts | 5 +--- libs/components/src/toast/toastr.component.ts | 4 +++ .../toggle-group/toggle-group.component.ts | 1 + .../src/toggle-group/toggle-group.module.ts | 6 +---- .../src/toggle-group/toggle.component.ts | 3 +++ .../src/typography/typography.directive.ts | 1 + .../src/typography/typography.module.ts | 4 +-- 101 files changed, 330 insertions(+), 216 deletions(-) diff --git a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.spec.ts b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.spec.ts index a07f56db2d..382ce8e026 100644 --- a/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.spec.ts +++ b/apps/web/src/app/layouts/product-switcher/navigation-switcher/navigation-switcher.component.spec.ts @@ -6,7 +6,7 @@ import { BehaviorSubject } from "rxjs"; import { I18nPipe } from "@bitwarden/angular/platform/pipes/i18n.pipe"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { BitIconButtonComponent } from "@bitwarden/components/src/icon-button/icon-button.component"; +import { IconButtonModule, NavigationModule } from "@bitwarden/components"; import { NavItemComponent } from "@bitwarden/components/src/navigation/nav-item.component"; import { ProductSwitcherItem, ProductSwitcherService } from "../shared/product-switcher.service"; @@ -45,13 +45,8 @@ describe("NavigationProductSwitcherComponent", () => { mockProducts$.next({ bento: [], other: [] }); await TestBed.configureTestingModule({ - imports: [RouterModule], - declarations: [ - NavigationProductSwitcherComponent, - NavItemComponent, - BitIconButtonComponent, - I18nPipe, - ], + imports: [RouterModule, NavigationModule, IconButtonModule], + declarations: [NavigationProductSwitcherComponent, I18nPipe], providers: [ { provide: ProductSwitcherService, useValue: productSwitcherService }, { diff --git a/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health.component.spec.ts b/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health.component.spec.ts index 98637d0dec..1f1756731f 100644 --- a/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health.component.spec.ts +++ b/bitwarden_license/bit-web/src/app/tools/access-intelligence/password-health.component.spec.ts @@ -13,7 +13,6 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service"; import { TableModule } from "@bitwarden/components"; -import { TableBodyDirective } from "@bitwarden/components/src/table/table.component"; import { LooseComponentsModule } from "@bitwarden/web-vault/app/shared"; import { PipesModule } from "@bitwarden/web-vault/app/vault/individual-vault/pipes/pipes.module"; @@ -27,7 +26,7 @@ describe("PasswordHealthComponent", () => { beforeEach(async () => { await TestBed.configureTestingModule({ imports: [PasswordHealthComponent, PipesModule, TableModule, LooseComponentsModule], - declarations: [TableBodyDirective], + declarations: [], providers: [ { provide: CipherService, useValue: mock() }, { provide: I18nService, useValue: mock() }, diff --git a/libs/components/src/async-actions/async-actions.module.ts b/libs/components/src/async-actions/async-actions.module.ts index 8ff1deb278..bff4286f89 100644 --- a/libs/components/src/async-actions/async-actions.module.ts +++ b/libs/components/src/async-actions/async-actions.module.ts @@ -1,14 +1,11 @@ import { NgModule } from "@angular/core"; -import { SharedModule } from "../shared"; - import { BitActionDirective } from "./bit-action.directive"; import { BitSubmitDirective } from "./bit-submit.directive"; import { BitFormButtonDirective } from "./form-button.directive"; @NgModule({ - imports: [SharedModule], - declarations: [BitActionDirective, BitFormButtonDirective, BitSubmitDirective], + imports: [BitActionDirective, BitFormButtonDirective, BitSubmitDirective], exports: [BitActionDirective, BitFormButtonDirective, BitSubmitDirective], }) export class AsyncActionsModule {} diff --git a/libs/components/src/async-actions/bit-action.directive.ts b/libs/components/src/async-actions/bit-action.directive.ts index 32ac73f418..3e793ae2ec 100644 --- a/libs/components/src/async-actions/bit-action.directive.ts +++ b/libs/components/src/async-actions/bit-action.directive.ts @@ -15,6 +15,7 @@ import { FunctionReturningAwaitable, functionToObservable } from "../utils/funct */ @Directive({ selector: "[bitAction]", + standalone: true, }) export class BitActionDirective implements OnDestroy { private destroy$ = new Subject(); diff --git a/libs/components/src/async-actions/bit-submit.directive.ts b/libs/components/src/async-actions/bit-submit.directive.ts index 838d78af8b..a38e76aaca 100644 --- a/libs/components/src/async-actions/bit-submit.directive.ts +++ b/libs/components/src/async-actions/bit-submit.directive.ts @@ -14,6 +14,7 @@ import { FunctionReturningAwaitable, functionToObservable } from "../utils/funct */ @Directive({ selector: "[formGroup][bitSubmit]", + standalone: true, }) export class BitSubmitDirective implements OnInit, OnDestroy { private destroy$ = new Subject(); diff --git a/libs/components/src/async-actions/form-button.directive.ts b/libs/components/src/async-actions/form-button.directive.ts index e468518869..7c92865b98 100644 --- a/libs/components/src/async-actions/form-button.directive.ts +++ b/libs/components/src/async-actions/form-button.directive.ts @@ -25,6 +25,7 @@ import { BitSubmitDirective } from "./bit-submit.directive"; */ @Directive({ selector: "button[bitFormButton]", + standalone: true, }) export class BitFormButtonDirective implements OnDestroy { private destroy$ = new Subject(); diff --git a/libs/components/src/avatar/avatar.component.ts b/libs/components/src/avatar/avatar.component.ts index e1758d795d..76ff702e88 100644 --- a/libs/components/src/avatar/avatar.component.ts +++ b/libs/components/src/avatar/avatar.component.ts @@ -1,5 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { NgIf, NgClass } from "@angular/common"; import { Component, Input, OnChanges } from "@angular/core"; import { DomSanitizer, SafeResourceUrl } from "@angular/platform-browser"; @@ -18,6 +19,8 @@ const SizeClasses: Record = { @Component({ selector: "bit-avatar", template: ``, + standalone: true, + imports: [NgIf, NgClass], }) export class AvatarComponent implements OnChanges { @Input() border = false; diff --git a/libs/components/src/avatar/avatar.module.ts b/libs/components/src/avatar/avatar.module.ts index ea78ff3a1d..4ef0978cbe 100644 --- a/libs/components/src/avatar/avatar.module.ts +++ b/libs/components/src/avatar/avatar.module.ts @@ -1,11 +1,9 @@ -import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; import { AvatarComponent } from "./avatar.component"; @NgModule({ - imports: [CommonModule], + imports: [AvatarComponent], exports: [AvatarComponent], - declarations: [AvatarComponent], }) export class AvatarModule {} diff --git a/libs/components/src/badge-list/badge-list.component.ts b/libs/components/src/badge-list/badge-list.component.ts index 9270e5e123..ac8cb3281a 100644 --- a/libs/components/src/badge-list/badge-list.component.ts +++ b/libs/components/src/badge-list/badge-list.component.ts @@ -1,12 +1,16 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { CommonModule } from "@angular/common"; import { Component, Input, OnChanges } from "@angular/core"; -import { BadgeVariant } from "../badge"; +import { BadgeModule, BadgeVariant } from "../badge"; +import { I18nPipe } from "../shared/i18n.pipe"; @Component({ selector: "bit-badge-list", templateUrl: "badge-list.component.html", + standalone: true, + imports: [CommonModule, BadgeModule, I18nPipe], }) export class BadgeListComponent implements OnChanges { private _maxItems: number; diff --git a/libs/components/src/badge-list/badge-list.module.ts b/libs/components/src/badge-list/badge-list.module.ts index d2a4ce211b..9359fe2c5c 100644 --- a/libs/components/src/badge-list/badge-list.module.ts +++ b/libs/components/src/badge-list/badge-list.module.ts @@ -1,13 +1,9 @@ import { NgModule } from "@angular/core"; -import { BadgeModule } from "../badge"; -import { SharedModule } from "../shared"; - import { BadgeListComponent } from "./badge-list.component"; @NgModule({ - imports: [SharedModule, BadgeModule], + imports: [BadgeListComponent], exports: [BadgeListComponent], - declarations: [BadgeListComponent], }) export class BadgeListModule {} diff --git a/libs/components/src/badge/badge.directive.ts b/libs/components/src/badge/badge.directive.ts index f39f8f8763..eef876a664 100644 --- a/libs/components/src/badge/badge.directive.ts +++ b/libs/components/src/badge/badge.directive.ts @@ -31,6 +31,7 @@ const hoverStyles: Record = { @Directive({ selector: "span[bitBadge], a[bitBadge], button[bitBadge]", providers: [{ provide: FocusableElement, useExisting: BadgeDirective }], + standalone: true, }) export class BadgeDirective implements FocusableElement { @HostBinding("class") get classList() { diff --git a/libs/components/src/badge/badge.module.ts b/libs/components/src/badge/badge.module.ts index e1b8292363..e7f3770785 100644 --- a/libs/components/src/badge/badge.module.ts +++ b/libs/components/src/badge/badge.module.ts @@ -1,11 +1,9 @@ -import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; import { BadgeDirective } from "./badge.directive"; @NgModule({ - imports: [CommonModule], + imports: [BadgeDirective], exports: [BadgeDirective], - declarations: [BadgeDirective], }) export class BadgeModule {} diff --git a/libs/components/src/banner/banner.component.spec.ts b/libs/components/src/banner/banner.component.spec.ts index 29f10016a1..2bbc796564 100644 --- a/libs/components/src/banner/banner.component.spec.ts +++ b/libs/components/src/banner/banner.component.spec.ts @@ -2,7 +2,6 @@ import { ComponentFixture, TestBed } from "@angular/core/testing"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { SharedModule } from "../shared/shared.module"; import { I18nMockService } from "../utils/i18n-mock.service"; import { BannerComponent } from "./banner.component"; @@ -13,8 +12,7 @@ describe("BannerComponent", () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [SharedModule], - declarations: [BannerComponent], + imports: [BannerComponent], providers: [ { provide: I18nService, diff --git a/libs/components/src/banner/banner.component.ts b/libs/components/src/banner/banner.component.ts index 7d59ceb0ee..d3f6432997 100644 --- a/libs/components/src/banner/banner.component.ts +++ b/libs/components/src/banner/banner.component.ts @@ -1,7 +1,11 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { CommonModule } from "@angular/common"; import { Component, Input, OnInit, Output, EventEmitter } from "@angular/core"; +import { IconButtonModule } from "../icon-button"; +import { I18nPipe } from "../shared/i18n.pipe"; + type BannerTypes = "premium" | "info" | "warning" | "danger"; const defaultIcon: Record = { @@ -14,6 +18,8 @@ const defaultIcon: Record = { @Component({ selector: "bit-banner", templateUrl: "./banner.component.html", + standalone: true, + imports: [CommonModule, IconButtonModule, I18nPipe], }) export class BannerComponent implements OnInit { @Input("bannerType") bannerType: BannerTypes = "info"; diff --git a/libs/components/src/banner/banner.module.ts b/libs/components/src/banner/banner.module.ts index 2c819fbc5b..3301218ed1 100644 --- a/libs/components/src/banner/banner.module.ts +++ b/libs/components/src/banner/banner.module.ts @@ -1,14 +1,9 @@ -import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; -import { IconButtonModule } from "../icon-button"; -import { SharedModule } from "../shared/shared.module"; - import { BannerComponent } from "./banner.component"; @NgModule({ - imports: [CommonModule, SharedModule, IconButtonModule], + imports: [BannerComponent], exports: [BannerComponent], - declarations: [BannerComponent], }) export class BannerModule {} diff --git a/libs/components/src/breadcrumbs/breadcrumb.component.ts b/libs/components/src/breadcrumbs/breadcrumb.component.ts index d612854044..ce18bde171 100644 --- a/libs/components/src/breadcrumbs/breadcrumb.component.ts +++ b/libs/components/src/breadcrumbs/breadcrumb.component.ts @@ -1,11 +1,14 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { NgIf } from "@angular/common"; import { Component, EventEmitter, Input, Output, TemplateRef, ViewChild } from "@angular/core"; import { QueryParamsHandling } from "@angular/router"; @Component({ selector: "bit-breadcrumb", templateUrl: "./breadcrumb.component.html", + standalone: true, + imports: [NgIf], }) export class BreadcrumbComponent { @Input() diff --git a/libs/components/src/breadcrumbs/breadcrumbs.component.ts b/libs/components/src/breadcrumbs/breadcrumbs.component.ts index 64ca8146c8..6e8fbf5c25 100644 --- a/libs/components/src/breadcrumbs/breadcrumbs.component.ts +++ b/libs/components/src/breadcrumbs/breadcrumbs.component.ts @@ -1,10 +1,18 @@ +import { CommonModule } from "@angular/common"; import { Component, ContentChildren, Input, QueryList } from "@angular/core"; +import { RouterModule } from "@angular/router"; + +import { IconButtonModule } from "../icon-button"; +import { LinkModule } from "../link"; +import { MenuModule } from "../menu"; import { BreadcrumbComponent } from "./breadcrumb.component"; @Component({ selector: "bit-breadcrumbs", templateUrl: "./breadcrumbs.component.html", + standalone: true, + imports: [CommonModule, LinkModule, RouterModule, IconButtonModule, MenuModule], }) export class BreadcrumbsComponent { @Input() diff --git a/libs/components/src/breadcrumbs/breadcrumbs.module.ts b/libs/components/src/breadcrumbs/breadcrumbs.module.ts index 0812b552f9..89b57fd19b 100644 --- a/libs/components/src/breadcrumbs/breadcrumbs.module.ts +++ b/libs/components/src/breadcrumbs/breadcrumbs.module.ts @@ -1,17 +1,10 @@ -import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; -import { RouterModule } from "@angular/router"; - -import { IconButtonModule } from "../icon-button"; -import { LinkModule } from "../link"; -import { MenuModule } from "../menu"; import { BreadcrumbComponent } from "./breadcrumb.component"; import { BreadcrumbsComponent } from "./breadcrumbs.component"; @NgModule({ - imports: [CommonModule, LinkModule, IconButtonModule, MenuModule, RouterModule], - declarations: [BreadcrumbsComponent, BreadcrumbComponent], + imports: [BreadcrumbsComponent, BreadcrumbComponent], exports: [BreadcrumbsComponent, BreadcrumbComponent], }) export class BreadcrumbsModule {} diff --git a/libs/components/src/button/button.component.ts b/libs/components/src/button/button.component.ts index 67b57d576a..96311f9152 100644 --- a/libs/components/src/button/button.component.ts +++ b/libs/components/src/button/button.component.ts @@ -1,6 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { coerceBooleanProperty } from "@angular/cdk/coercion"; +import { NgClass } from "@angular/common"; import { Input, HostBinding, Component } from "@angular/core"; import { ButtonLikeAbstraction, ButtonType } from "../shared/button-like.abstraction"; @@ -46,6 +47,8 @@ const buttonStyles: Record = { selector: "button[bitButton], a[bitButton]", templateUrl: "button.component.html", providers: [{ provide: ButtonLikeAbstraction, useExisting: ButtonComponent }], + standalone: true, + imports: [NgClass], }) export class ButtonComponent implements ButtonLikeAbstraction { @HostBinding("class") get classList() { diff --git a/libs/components/src/button/button.module.ts b/libs/components/src/button/button.module.ts index 448e7c9dcf..f1a86eff3a 100644 --- a/libs/components/src/button/button.module.ts +++ b/libs/components/src/button/button.module.ts @@ -1,11 +1,9 @@ -import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; import { ButtonComponent } from "./button.component"; @NgModule({ - imports: [CommonModule], + imports: [ButtonComponent], exports: [ButtonComponent], - declarations: [ButtonComponent], }) export class ButtonModule {} diff --git a/libs/components/src/checkbox/checkbox.component.ts b/libs/components/src/checkbox/checkbox.component.ts index 1ca27e84b8..0ce6f1889b 100644 --- a/libs/components/src/checkbox/checkbox.component.ts +++ b/libs/components/src/checkbox/checkbox.component.ts @@ -9,6 +9,7 @@ import { BitFormControlAbstraction } from "../form-control"; selector: "input[type=checkbox][bitCheckbox]", template: "", providers: [{ provide: BitFormControlAbstraction, useExisting: CheckboxComponent }], + standalone: true, }) export class CheckboxComponent implements BitFormControlAbstraction { @HostBinding("class") diff --git a/libs/components/src/checkbox/checkbox.module.ts b/libs/components/src/checkbox/checkbox.module.ts index d03b9cf505..3abfb4b1bf 100644 --- a/libs/components/src/checkbox/checkbox.module.ts +++ b/libs/components/src/checkbox/checkbox.module.ts @@ -1,14 +1,9 @@ -import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; -import { FormControlModule } from "../form-control"; -import { SharedModule } from "../shared"; - import { CheckboxComponent } from "./checkbox.component"; @NgModule({ - imports: [SharedModule, CommonModule, FormControlModule], - declarations: [CheckboxComponent], + imports: [CheckboxComponent], exports: [CheckboxComponent], }) export class CheckboxModule {} diff --git a/libs/components/src/color-password/color-password.component.ts b/libs/components/src/color-password/color-password.component.ts index 35732760ac..cbf746e9d7 100644 --- a/libs/components/src/color-password/color-password.component.ts +++ b/libs/components/src/color-password/color-password.component.ts @@ -1,5 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { NgFor, NgIf } from "@angular/common"; import { Component, HostBinding, Input } from "@angular/core"; import { Utils } from "@bitwarden/common/platform/misc/utils"; @@ -23,6 +24,8 @@ enum CharacterType { }} `, preserveWhitespaces: false, + standalone: true, + imports: [NgFor, NgIf], }) export class ColorPasswordComponent { @Input() password: string = null; diff --git a/libs/components/src/color-password/color-password.module.ts b/libs/components/src/color-password/color-password.module.ts index 692c206bb4..3ebc1c80e1 100644 --- a/libs/components/src/color-password/color-password.module.ts +++ b/libs/components/src/color-password/color-password.module.ts @@ -1,11 +1,9 @@ -import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; import { ColorPasswordComponent } from "./color-password.component"; @NgModule({ - imports: [CommonModule], + imports: [ColorPasswordComponent], exports: [ColorPasswordComponent], - declarations: [ColorPasswordComponent], }) export class ColorPasswordModule {} diff --git a/libs/components/src/dialog/dialog.module.ts b/libs/components/src/dialog/dialog.module.ts index bc37f749c0..f31fdd5206 100644 --- a/libs/components/src/dialog/dialog.module.ts +++ b/libs/components/src/dialog/dialog.module.ts @@ -1,44 +1,25 @@ import { DialogModule as CdkDialogModule } from "@angular/cdk/dialog"; import { NgModule } from "@angular/core"; -import { ReactiveFormsModule } from "@angular/forms"; - -import { AsyncActionsModule } from "../async-actions"; -import { ButtonModule } from "../button"; -import { IconButtonModule } from "../icon-button"; -import { SharedModule } from "../shared"; -import { TypographyModule } from "../typography"; import { DialogComponent } from "./dialog/dialog.component"; import { DialogService } from "./dialog.service"; import { DialogCloseDirective } from "./directives/dialog-close.directive"; -import { DialogTitleContainerDirective } from "./directives/dialog-title-container.directive"; -import { SimpleConfigurableDialogComponent } from "./simple-dialog/simple-configurable-dialog/simple-configurable-dialog.component"; import { IconDirective, SimpleDialogComponent } from "./simple-dialog/simple-dialog.component"; @NgModule({ imports: [ - SharedModule, - AsyncActionsModule, - ButtonModule, CdkDialogModule, - IconButtonModule, - ReactiveFormsModule, - TypographyModule, - ], - declarations: [ DialogCloseDirective, - DialogTitleContainerDirective, DialogComponent, SimpleDialogComponent, - SimpleConfigurableDialogComponent, IconDirective, ], exports: [ CdkDialogModule, - DialogComponent, - SimpleDialogComponent, DialogCloseDirective, + DialogComponent, IconDirective, + SimpleDialogComponent, ], providers: [DialogService], }) diff --git a/libs/components/src/dialog/dialog/dialog.component.ts b/libs/components/src/dialog/dialog/dialog.component.ts index 2f901d10d2..ed47201805 100644 --- a/libs/components/src/dialog/dialog/dialog.component.ts +++ b/libs/components/src/dialog/dialog/dialog.component.ts @@ -1,14 +1,29 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { coerceBooleanProperty } from "@angular/cdk/coercion"; +import { CommonModule } from "@angular/common"; import { Component, HostBinding, Input } from "@angular/core"; +import { BitIconButtonComponent } from "../../icon-button/icon-button.component"; +import { I18nPipe } from "../../shared/i18n.pipe"; +import { TypographyDirective } from "../../typography/typography.directive"; import { fadeIn } from "../animations"; +import { DialogCloseDirective } from "../directives/dialog-close.directive"; +import { DialogTitleContainerDirective } from "../directives/dialog-title-container.directive"; @Component({ selector: "bit-dialog", templateUrl: "./dialog.component.html", animations: [fadeIn], + standalone: true, + imports: [ + CommonModule, + DialogTitleContainerDirective, + TypographyDirective, + BitIconButtonComponent, + DialogCloseDirective, + I18nPipe, + ], }) export class DialogComponent { /** Background color */ diff --git a/libs/components/src/dialog/directives/dialog-close.directive.ts b/libs/components/src/dialog/directives/dialog-close.directive.ts index 5e44ced7c2..5e5fda3e01 100644 --- a/libs/components/src/dialog/directives/dialog-close.directive.ts +++ b/libs/components/src/dialog/directives/dialog-close.directive.ts @@ -3,6 +3,7 @@ import { Directive, HostBinding, HostListener, Input, Optional } from "@angular/ @Directive({ selector: "[bitDialogClose]", + standalone: true, }) export class DialogCloseDirective { @Input("bitDialogClose") dialogResult: any; diff --git a/libs/components/src/dialog/directives/dialog-title-container.directive.ts b/libs/components/src/dialog/directives/dialog-title-container.directive.ts index e17487f278..cf46396967 100644 --- a/libs/components/src/dialog/directives/dialog-title-container.directive.ts +++ b/libs/components/src/dialog/directives/dialog-title-container.directive.ts @@ -6,6 +6,7 @@ let nextId = 0; @Directive({ selector: "[bitDialogTitleContainer]", + standalone: true, }) export class DialogTitleContainerDirective implements OnInit { @HostBinding("id") id = `bit-dialog-title-${nextId++}`; diff --git a/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.component.ts b/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.component.ts index 29d52e9cf0..60b2e1c3a3 100644 --- a/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.component.ts +++ b/libs/components/src/dialog/simple-dialog/simple-configurable-dialog/simple-configurable-dialog.component.ts @@ -1,12 +1,17 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog"; +import { NgIf } from "@angular/common"; import { Component, Inject } from "@angular/core"; -import { FormGroup } from "@angular/forms"; +import { FormGroup, ReactiveFormsModule } from "@angular/forms"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { SimpleDialogOptions, SimpleDialogType, Translation } from "../.."; +import { BitSubmitDirective } from "../../../async-actions/bit-submit.directive"; +import { BitFormButtonDirective } from "../../../async-actions/form-button.directive"; +import { ButtonComponent } from "../../../button/button.component"; +import { SimpleDialogComponent, IconDirective } from "../simple-dialog.component"; const DEFAULT_ICON: Record = { primary: "bwi-business", @@ -26,6 +31,16 @@ const DEFAULT_COLOR: Record = { @Component({ templateUrl: "./simple-configurable-dialog.component.html", + standalone: true, + imports: [ + ReactiveFormsModule, + BitSubmitDirective, + SimpleDialogComponent, + IconDirective, + ButtonComponent, + BitFormButtonDirective, + NgIf, + ], }) export class SimpleConfigurableDialogComponent { get iconClasses() { diff --git a/libs/components/src/dialog/simple-dialog/simple-dialog.component.ts b/libs/components/src/dialog/simple-dialog/simple-dialog.component.ts index 912b0299f6..c02a13bd15 100644 --- a/libs/components/src/dialog/simple-dialog/simple-dialog.component.ts +++ b/libs/components/src/dialog/simple-dialog/simple-dialog.component.ts @@ -1,14 +1,22 @@ +import { NgIf } from "@angular/common"; import { Component, ContentChild, Directive } from "@angular/core"; +import { TypographyDirective } from "../../typography/typography.directive"; import { fadeIn } from "../animations"; +import { DialogTitleContainerDirective } from "../directives/dialog-title-container.directive"; -@Directive({ selector: "[bitDialogIcon]" }) +@Directive({ + selector: "[bitDialogIcon]", + standalone: true, +}) export class IconDirective {} @Component({ selector: "bit-simple-dialog", templateUrl: "./simple-dialog.component.html", animations: [fadeIn], + standalone: true, + imports: [NgIf, DialogTitleContainerDirective, TypographyDirective], }) export class SimpleDialogComponent { @ContentChild(IconDirective) icon!: IconDirective; diff --git a/libs/components/src/form-control/form-control.component.ts b/libs/components/src/form-control/form-control.component.ts index 6c24e7a53e..9b87c44157 100644 --- a/libs/components/src/form-control/form-control.component.ts +++ b/libs/components/src/form-control/form-control.component.ts @@ -1,15 +1,21 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { coerceBooleanProperty } from "@angular/cdk/coercion"; +import { NgClass, NgIf } from "@angular/common"; import { Component, ContentChild, HostBinding, Input } from "@angular/core"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { I18nPipe } from "../shared/i18n.pipe"; +import { TypographyDirective } from "../typography/typography.directive"; + import { BitFormControlAbstraction } from "./form-control.abstraction"; @Component({ selector: "bit-form-control", templateUrl: "form-control.component.html", + standalone: true, + imports: [NgClass, TypographyDirective, NgIf, I18nPipe], }) export class FormControlComponent { @Input() label: string; diff --git a/libs/components/src/form-control/form-control.module.ts b/libs/components/src/form-control/form-control.module.ts index f6969a97e9..df168d8e98 100644 --- a/libs/components/src/form-control/form-control.module.ts +++ b/libs/components/src/form-control/form-control.module.ts @@ -1,15 +1,11 @@ import { NgModule } from "@angular/core"; -import { SharedModule } from "../shared"; -import { TypographyModule } from "../typography"; - import { FormControlComponent } from "./form-control.component"; import { BitHintComponent } from "./hint.component"; import { BitLabel } from "./label.component"; @NgModule({ - imports: [SharedModule, BitLabel, TypographyModule], - declarations: [FormControlComponent, BitHintComponent], + imports: [BitLabel, FormControlComponent, BitHintComponent], exports: [FormControlComponent, BitLabel, BitHintComponent], }) export class FormControlModule {} diff --git a/libs/components/src/form-control/hint.component.ts b/libs/components/src/form-control/hint.component.ts index c1f21bf254..4fee0d4560 100644 --- a/libs/components/src/form-control/hint.component.ts +++ b/libs/components/src/form-control/hint.component.ts @@ -8,6 +8,7 @@ let nextId = 0; host: { class: "tw-text-muted tw-font-normal tw-inline-block tw-mt-1 tw-text-xs", }, + standalone: true, }) export class BitHintComponent { @HostBinding() id = `bit-hint-${nextId++}`; diff --git a/libs/components/src/form-field/error-summary.component.ts b/libs/components/src/form-field/error-summary.component.ts index f374740b20..beed32a88a 100644 --- a/libs/components/src/form-field/error-summary.component.ts +++ b/libs/components/src/form-field/error-summary.component.ts @@ -1,8 +1,11 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { NgIf } from "@angular/common"; import { Component, Input } from "@angular/core"; import { AbstractControl, UntypedFormGroup } from "@angular/forms"; +import { I18nPipe } from "../shared/i18n.pipe"; + @Component({ selector: "bit-error-summary", template: ` 0"> @@ -12,6 +15,8 @@ import { AbstractControl, UntypedFormGroup } from "@angular/forms"; class: "tw-block tw-text-danger tw-mt-2", "aria-live": "assertive", }, + standalone: true, + imports: [NgIf, I18nPipe], }) export class BitErrorSummary { @Input() diff --git a/libs/components/src/form-field/error.component.ts b/libs/components/src/form-field/error.component.ts index a0f7906b36..27adbf7d31 100644 --- a/libs/components/src/form-field/error.component.ts +++ b/libs/components/src/form-field/error.component.ts @@ -14,6 +14,7 @@ let nextId = 0; class: "tw-block tw-mt-1 tw-text-danger tw-text-xs", "aria-live": "assertive", }, + standalone: true, }) export class BitErrorComponent { @HostBinding() id = `bit-error-${nextId++}`; diff --git a/libs/components/src/form-field/form-field.component.ts b/libs/components/src/form-field/form-field.component.ts index 6f425e4149..9f41c6cf6a 100644 --- a/libs/components/src/form-field/form-field.component.ts +++ b/libs/components/src/form-field/form-field.component.ts @@ -1,5 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { CommonModule } from "@angular/common"; import { AfterContentChecked, booleanAttribute, @@ -16,6 +17,7 @@ import { import { BitHintComponent } from "../form-control/hint.component"; import { BitLabel } from "../form-control/label.component"; import { inputBorderClasses } from "../input/input.directive"; +import { I18nPipe } from "../shared/i18n.pipe"; import { BitErrorComponent } from "./error.component"; import { BitFormFieldControl } from "./form-field-control"; @@ -23,6 +25,8 @@ import { BitFormFieldControl } from "./form-field-control"; @Component({ selector: "bit-form-field", templateUrl: "./form-field.component.html", + standalone: true, + imports: [CommonModule, BitErrorComponent, I18nPipe], }) export class BitFormFieldComponent implements AfterContentChecked { @ContentChild(BitFormFieldControl) input: BitFormFieldControl; diff --git a/libs/components/src/form-field/form-field.module.ts b/libs/components/src/form-field/form-field.module.ts index 989375167d..88d7ffcc78 100644 --- a/libs/components/src/form-field/form-field.module.ts +++ b/libs/components/src/form-field/form-field.module.ts @@ -1,11 +1,8 @@ import { NgModule } from "@angular/core"; import { FormControlModule } from "../form-control"; -import { BitInputDirective } from "../input/input.directive"; import { InputModule } from "../input/input.module"; -import { MultiSelectComponent } from "../multi-select/multi-select.component"; import { MultiSelectModule } from "../multi-select/multi-select.module"; -import { SharedModule } from "../shared"; import { BitErrorSummary } from "./error-summary.component"; import { BitErrorComponent } from "./error.component"; @@ -15,8 +12,11 @@ import { BitPrefixDirective } from "./prefix.directive"; import { BitSuffixDirective } from "./suffix.directive"; @NgModule({ - imports: [SharedModule, FormControlModule, InputModule, MultiSelectModule], - declarations: [ + imports: [ + FormControlModule, + InputModule, + MultiSelectModule, + BitErrorComponent, BitErrorSummary, BitFormFieldComponent, @@ -25,15 +25,16 @@ import { BitSuffixDirective } from "./suffix.directive"; BitSuffixDirective, ], exports: [ + FormControlModule, + InputModule, + MultiSelectModule, + BitErrorComponent, BitErrorSummary, BitFormFieldComponent, - BitInputDirective, BitPasswordInputToggleDirective, BitPrefixDirective, BitSuffixDirective, - MultiSelectComponent, - FormControlModule, ], }) export class FormFieldModule {} diff --git a/libs/components/src/form-field/password-input-toggle.directive.ts b/libs/components/src/form-field/password-input-toggle.directive.ts index a696a88c46..933722db5b 100644 --- a/libs/components/src/form-field/password-input-toggle.directive.ts +++ b/libs/components/src/form-field/password-input-toggle.directive.ts @@ -18,6 +18,7 @@ import { BitFormFieldComponent } from "./form-field.component"; @Directive({ selector: "[bitPasswordInputToggle]", + standalone: true, }) export class BitPasswordInputToggleDirective implements AfterContentInit, OnChanges { /** diff --git a/libs/components/src/form-field/prefix.directive.ts b/libs/components/src/form-field/prefix.directive.ts index 34fcbf8523..b44e90cbaa 100644 --- a/libs/components/src/form-field/prefix.directive.ts +++ b/libs/components/src/form-field/prefix.directive.ts @@ -4,6 +4,7 @@ import { BitIconButtonComponent } from "../icon-button/icon-button.component"; @Directive({ selector: "[bitPrefix]", + standalone: true, }) export class BitPrefixDirective implements OnInit { @HostBinding("class") @Input() get classList() { diff --git a/libs/components/src/form-field/suffix.directive.ts b/libs/components/src/form-field/suffix.directive.ts index 28736ce78a..baf1afce76 100644 --- a/libs/components/src/form-field/suffix.directive.ts +++ b/libs/components/src/form-field/suffix.directive.ts @@ -4,6 +4,7 @@ import { BitIconButtonComponent } from "../icon-button/icon-button.component"; @Directive({ selector: "[bitSuffix]", + standalone: true, }) export class BitSuffixDirective implements OnInit { @HostBinding("class") @Input() get classList() { diff --git a/libs/components/src/icon-button/icon-button.component.ts b/libs/components/src/icon-button/icon-button.component.ts index 97016f9fd0..ac7dff0408 100644 --- a/libs/components/src/icon-button/icon-button.component.ts +++ b/libs/components/src/icon-button/icon-button.component.ts @@ -1,5 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { NgClass } from "@angular/common"; import { Component, ElementRef, HostBinding, Input } from "@angular/core"; import { ButtonLikeAbstraction, ButtonType } from "../shared/button-like.abstraction"; @@ -134,6 +135,8 @@ const sizes: Record = { { provide: ButtonLikeAbstraction, useExisting: BitIconButtonComponent }, { provide: FocusableElement, useExisting: BitIconButtonComponent }, ], + standalone: true, + imports: [NgClass], }) export class BitIconButtonComponent implements ButtonLikeAbstraction, FocusableElement { @Input("bitIconButton") icon: string; diff --git a/libs/components/src/icon-button/icon-button.module.ts b/libs/components/src/icon-button/icon-button.module.ts index fb4e858971..26f48cdb17 100644 --- a/libs/components/src/icon-button/icon-button.module.ts +++ b/libs/components/src/icon-button/icon-button.module.ts @@ -1,11 +1,9 @@ -import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; import { BitIconButtonComponent } from "./icon-button.component"; @NgModule({ - imports: [CommonModule], - declarations: [BitIconButtonComponent], + imports: [BitIconButtonComponent], exports: [BitIconButtonComponent], }) export class IconButtonModule {} diff --git a/libs/components/src/icon/icon.component.ts b/libs/components/src/icon/icon.component.ts index 55615d4dae..2382d197be 100644 --- a/libs/components/src/icon/icon.component.ts +++ b/libs/components/src/icon/icon.component.ts @@ -8,6 +8,7 @@ import { Icon, isIcon } from "./icon"; @Component({ selector: "bit-icon", template: ``, + standalone: true, }) export class BitIconComponent { @Input() set icon(icon: Icon) { diff --git a/libs/components/src/icon/icon.components.spec.ts b/libs/components/src/icon/icon.components.spec.ts index 351ed5f021..7d499cdd41 100644 --- a/libs/components/src/icon/icon.components.spec.ts +++ b/libs/components/src/icon/icon.components.spec.ts @@ -9,7 +9,7 @@ describe("IconComponent", () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [BitIconComponent], + imports: [BitIconComponent], }).compileComponents(); fixture = TestBed.createComponent(BitIconComponent); diff --git a/libs/components/src/icon/icon.module.ts b/libs/components/src/icon/icon.module.ts index 32e95fd046..3d15b5bb3c 100644 --- a/libs/components/src/icon/icon.module.ts +++ b/libs/components/src/icon/icon.module.ts @@ -1,11 +1,9 @@ -import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; import { BitIconComponent } from "./icon.component"; @NgModule({ - imports: [CommonModule], - declarations: [BitIconComponent], + imports: [BitIconComponent], exports: [BitIconComponent], }) export class IconModule {} diff --git a/libs/components/src/input/input.directive.ts b/libs/components/src/input/input.directive.ts index 4a6a03295d..f6c6c3d542 100644 --- a/libs/components/src/input/input.directive.ts +++ b/libs/components/src/input/input.directive.ts @@ -30,6 +30,7 @@ export function inputBorderClasses(error: boolean) { @Directive({ selector: "input[bitInput], select[bitInput], textarea[bitInput]", providers: [{ provide: BitFormFieldControl, useExisting: BitInputDirective }], + standalone: true, }) export class BitInputDirective implements BitFormFieldControl { @HostBinding("class") @Input() get classList() { diff --git a/libs/components/src/input/input.module.ts b/libs/components/src/input/input.module.ts index cfc49cefb7..9399cb0651 100644 --- a/libs/components/src/input/input.module.ts +++ b/libs/components/src/input/input.module.ts @@ -1,11 +1,9 @@ -import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; import { BitInputDirective } from "./input.directive"; @NgModule({ - imports: [CommonModule], - declarations: [BitInputDirective], + imports: [BitInputDirective], exports: [BitInputDirective], }) export class InputModule {} diff --git a/libs/components/src/link/link.directive.ts b/libs/components/src/link/link.directive.ts index b127d80fed..52aba55766 100644 --- a/libs/components/src/link/link.directive.ts +++ b/libs/components/src/link/link.directive.ts @@ -68,6 +68,7 @@ abstract class LinkDirective { @Directive({ selector: "a[bitLink]", + standalone: true, }) export class AnchorLinkDirective extends LinkDirective { @HostBinding("class") get classList() { @@ -79,6 +80,7 @@ export class AnchorLinkDirective extends LinkDirective { @Directive({ selector: "button[bitLink]", + standalone: true, }) export class ButtonLinkDirective extends LinkDirective { @HostBinding("class") get classList() { diff --git a/libs/components/src/link/link.module.ts b/libs/components/src/link/link.module.ts index b8b54d57c0..52d2f29e53 100644 --- a/libs/components/src/link/link.module.ts +++ b/libs/components/src/link/link.module.ts @@ -1,11 +1,9 @@ -import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; import { AnchorLinkDirective, ButtonLinkDirective } from "./link.directive"; @NgModule({ - imports: [CommonModule], + imports: [AnchorLinkDirective, ButtonLinkDirective], exports: [AnchorLinkDirective, ButtonLinkDirective], - declarations: [AnchorLinkDirective, ButtonLinkDirective], }) export class LinkModule {} diff --git a/libs/components/src/menu/menu-divider.component.ts b/libs/components/src/menu/menu-divider.component.ts index 194506ee50..55b5c013c9 100644 --- a/libs/components/src/menu/menu-divider.component.ts +++ b/libs/components/src/menu/menu-divider.component.ts @@ -3,5 +3,6 @@ import { Component } from "@angular/core"; @Component({ selector: "bit-menu-divider", templateUrl: "./menu-divider.component.html", + standalone: true, }) export class MenuDividerComponent {} diff --git a/libs/components/src/menu/menu-item.directive.ts b/libs/components/src/menu/menu-item.directive.ts index 5fdc8fabfc..d0975e8e39 100644 --- a/libs/components/src/menu/menu-item.directive.ts +++ b/libs/components/src/menu/menu-item.directive.ts @@ -1,10 +1,13 @@ import { FocusableOption } from "@angular/cdk/a11y"; import { coerceBooleanProperty } from "@angular/cdk/coercion"; +import { NgClass } from "@angular/common"; import { Component, ElementRef, HostBinding, Input } from "@angular/core"; @Component({ selector: "[bitMenuItem]", templateUrl: "menu-item.component.html", + standalone: true, + imports: [NgClass], }) export class MenuItemDirective implements FocusableOption { @HostBinding("class") classList = [ diff --git a/libs/components/src/menu/menu-trigger-for.directive.ts b/libs/components/src/menu/menu-trigger-for.directive.ts index d318a77ef0..786554e981 100644 --- a/libs/components/src/menu/menu-trigger-for.directive.ts +++ b/libs/components/src/menu/menu-trigger-for.directive.ts @@ -19,6 +19,7 @@ import { MenuComponent } from "./menu.component"; @Directive({ selector: "[bitMenuTriggerFor]", exportAs: "menuTrigger", + standalone: true, }) export class MenuTriggerForDirective implements OnDestroy { @HostBinding("attr.aria-expanded") isOpen = false; diff --git a/libs/components/src/menu/menu.component.ts b/libs/components/src/menu/menu.component.ts index f0bf4f81df..a39dceb445 100644 --- a/libs/components/src/menu/menu.component.ts +++ b/libs/components/src/menu/menu.component.ts @@ -1,6 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { FocusKeyManager } from "@angular/cdk/a11y"; +import { FocusKeyManager, CdkTrapFocus } from "@angular/cdk/a11y"; import { Component, Output, @@ -19,6 +19,8 @@ import { MenuItemDirective } from "./menu-item.directive"; selector: "bit-menu", templateUrl: "./menu.component.html", exportAs: "menuComponent", + standalone: true, + imports: [CdkTrapFocus], }) export class MenuComponent implements AfterContentInit { @ViewChild(TemplateRef) templateRef: TemplateRef; diff --git a/libs/components/src/menu/menu.module.ts b/libs/components/src/menu/menu.module.ts index b165629e6c..117460df55 100644 --- a/libs/components/src/menu/menu.module.ts +++ b/libs/components/src/menu/menu.module.ts @@ -1,6 +1,3 @@ -import { A11yModule } from "@angular/cdk/a11y"; -import { OverlayModule } from "@angular/cdk/overlay"; -import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; import { MenuDividerComponent } from "./menu-divider.component"; @@ -9,8 +6,7 @@ import { MenuTriggerForDirective } from "./menu-trigger-for.directive"; import { MenuComponent } from "./menu.component"; @NgModule({ - imports: [A11yModule, CommonModule, OverlayModule], - declarations: [MenuComponent, MenuTriggerForDirective, MenuItemDirective, MenuDividerComponent], + imports: [MenuComponent, MenuTriggerForDirective, MenuItemDirective, MenuDividerComponent], exports: [MenuComponent, MenuTriggerForDirective, MenuItemDirective, MenuDividerComponent], }) export class MenuModule {} diff --git a/libs/components/src/menu/menu.stories.ts b/libs/components/src/menu/menu.stories.ts index c5d232b205..65fafd2d04 100644 --- a/libs/components/src/menu/menu.stories.ts +++ b/libs/components/src/menu/menu.stories.ts @@ -3,23 +3,15 @@ import { Meta, StoryObj, moduleMetadata } from "@storybook/angular"; import { ButtonModule } from "../button/button.module"; -import { MenuDividerComponent } from "./menu-divider.component"; -import { MenuItemDirective } from "./menu-item.directive"; import { MenuTriggerForDirective } from "./menu-trigger-for.directive"; -import { MenuComponent } from "./menu.component"; +import { MenuModule } from "./menu.module"; export default { title: "Component Library/Menu", component: MenuTriggerForDirective, decorators: [ moduleMetadata({ - declarations: [ - MenuTriggerForDirective, - MenuComponent, - MenuItemDirective, - MenuDividerComponent, - ], - imports: [OverlayModule, ButtonModule], + imports: [MenuModule, OverlayModule, ButtonModule], }), ], parameters: { @@ -51,7 +43,7 @@ export const OpenMenu: Story = { Disabled button - + @@ -67,7 +59,7 @@ export const ClosedMenu: Story = { Open menu - + Anchor link Another link diff --git a/libs/components/src/multi-select/multi-select.component.ts b/libs/components/src/multi-select/multi-select.component.ts index a18d5aa0b6..53e51bfe2f 100644 --- a/libs/components/src/multi-select/multi-select.component.ts +++ b/libs/components/src/multi-select/multi-select.component.ts @@ -2,6 +2,7 @@ // @ts-strict-ignore import { coerceBooleanProperty } from "@angular/cdk/coercion"; import { hasModifierKey } from "@angular/cdk/keycodes"; +import { NgIf } from "@angular/common"; import { Component, Input, @@ -13,12 +14,20 @@ import { Optional, Self, } from "@angular/core"; -import { ControlValueAccessor, NgControl, Validators } from "@angular/forms"; -import { NgSelectComponent } from "@ng-select/ng-select"; +import { + ControlValueAccessor, + NgControl, + Validators, + ReactiveFormsModule, + FormsModule, +} from "@angular/forms"; +import { NgSelectComponent, NgSelectModule } from "@ng-select/ng-select"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { BadgeModule } from "../badge"; import { BitFormFieldControl } from "../form-field/form-field-control"; +import { I18nPipe } from "../shared/i18n.pipe"; import { SelectItemView } from "./models/select-item-view"; @@ -29,6 +38,8 @@ let nextId = 0; selector: "bit-multi-select", templateUrl: "./multi-select.component.html", providers: [{ provide: BitFormFieldControl, useExisting: MultiSelectComponent }], + standalone: true, + imports: [NgSelectModule, ReactiveFormsModule, FormsModule, BadgeModule, NgIf, I18nPipe], }) /** * This component has been implemented to only support Multi-select list events diff --git a/libs/components/src/multi-select/multi-select.module.ts b/libs/components/src/multi-select/multi-select.module.ts index 88de53b548..c8cc899db0 100644 --- a/libs/components/src/multi-select/multi-select.module.ts +++ b/libs/components/src/multi-select/multi-select.module.ts @@ -1,16 +1,9 @@ -import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; -import { FormsModule } from "@angular/forms"; -import { NgSelectModule } from "@ng-select/ng-select"; - -import { BadgeModule } from "../badge"; -import { SharedModule } from "../shared"; import { MultiSelectComponent } from "./multi-select.component"; @NgModule({ - imports: [CommonModule, FormsModule, NgSelectModule, BadgeModule, SharedModule], + imports: [MultiSelectComponent], exports: [MultiSelectComponent], - declarations: [MultiSelectComponent], }) export class MultiSelectModule {} diff --git a/libs/components/src/navigation/nav-divider.component.ts b/libs/components/src/navigation/nav-divider.component.ts index 008d3f46c3..eff381e1c9 100644 --- a/libs/components/src/navigation/nav-divider.component.ts +++ b/libs/components/src/navigation/nav-divider.component.ts @@ -1,3 +1,4 @@ +import { CommonModule } from "@angular/common"; import { Component } from "@angular/core"; import { SideNavService } from "./side-nav.service"; @@ -5,6 +6,8 @@ import { SideNavService } from "./side-nav.service"; @Component({ selector: "bit-nav-divider", templateUrl: "./nav-divider.component.html", + standalone: true, + imports: [CommonModule], }) export class NavDividerComponent { constructor(protected sideNavService: SideNavService) {} diff --git a/libs/components/src/navigation/nav-group.component.ts b/libs/components/src/navigation/nav-group.component.ts index 07494c0b7d..58d93ddd3a 100644 --- a/libs/components/src/navigation/nav-group.component.ts +++ b/libs/components/src/navigation/nav-group.component.ts @@ -1,3 +1,4 @@ +import { CommonModule } from "@angular/common"; import { AfterContentInit, booleanAttribute, @@ -11,13 +12,22 @@ import { SkipSelf, } from "@angular/core"; +import { IconButtonModule } from "../icon-button"; +import { I18nPipe } from "../shared/i18n.pipe"; + import { NavBaseComponent } from "./nav-base.component"; +import { NavGroupAbstraction, NavItemComponent } from "./nav-item.component"; import { SideNavService } from "./side-nav.service"; @Component({ selector: "bit-nav-group", templateUrl: "./nav-group.component.html", - providers: [{ provide: NavBaseComponent, useExisting: NavGroupComponent }], + providers: [ + { provide: NavBaseComponent, useExisting: NavGroupComponent }, + { provide: NavGroupAbstraction, useExisting: NavGroupComponent }, + ], + standalone: true, + imports: [CommonModule, NavItemComponent, IconButtonModule, I18nPipe], }) export class NavGroupComponent extends NavBaseComponent implements AfterContentInit { @ContentChildren(NavBaseComponent, { diff --git a/libs/components/src/navigation/nav-item.component.ts b/libs/components/src/navigation/nav-item.component.ts index 8348638568..c8d464119c 100644 --- a/libs/components/src/navigation/nav-item.component.ts +++ b/libs/components/src/navigation/nav-item.component.ts @@ -1,14 +1,24 @@ +import { CommonModule } from "@angular/common"; import { Component, HostListener, Input, Optional } from "@angular/core"; +import { RouterModule } from "@angular/router"; import { BehaviorSubject, map } from "rxjs"; +import { IconButtonModule } from "../icon-button"; + import { NavBaseComponent } from "./nav-base.component"; -import { NavGroupComponent } from "./nav-group.component"; import { SideNavService } from "./side-nav.service"; +// Resolves a circular dependency between `NavItemComponent` and `NavItemGroup` when using standalone components. +export abstract class NavGroupAbstraction { + abstract setOpen(open: boolean): void; +} + @Component({ selector: "bit-nav-item", templateUrl: "./nav-item.component.html", providers: [{ provide: NavBaseComponent, useExisting: NavItemComponent }], + standalone: true, + imports: [CommonModule, IconButtonModule, RouterModule], }) export class NavItemComponent extends NavBaseComponent { /** Forces active styles to be shown, regardless of the `routerLinkActiveOptions` */ @@ -52,7 +62,7 @@ export class NavItemComponent extends NavBaseComponent { constructor( protected sideNavService: SideNavService, - @Optional() private parentNavGroup: NavGroupComponent, + @Optional() private parentNavGroup: NavGroupAbstraction, ) { super(); } diff --git a/libs/components/src/navigation/nav-logo.component.ts b/libs/components/src/navigation/nav-logo.component.ts index cbad5b869e..8a84970500 100644 --- a/libs/components/src/navigation/nav-logo.component.ts +++ b/libs/components/src/navigation/nav-logo.component.ts @@ -1,14 +1,20 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { NgIf } from "@angular/common"; import { Component, Input } from "@angular/core"; +import { RouterLinkActive, RouterLink } from "@angular/router"; import { Icon } from "../icon"; +import { BitIconComponent } from "../icon/icon.component"; +import { NavItemComponent } from "./nav-item.component"; import { SideNavService } from "./side-nav.service"; @Component({ selector: "bit-nav-logo", templateUrl: "./nav-logo.component.html", + standalone: true, + imports: [NgIf, RouterLinkActive, RouterLink, BitIconComponent, NavItemComponent], }) export class NavLogoComponent { /** Icon that is displayed when the side nav is closed */ diff --git a/libs/components/src/navigation/navigation.module.ts b/libs/components/src/navigation/navigation.module.ts index 852bd1c0a2..a08fbaddb9 100644 --- a/libs/components/src/navigation/navigation.module.ts +++ b/libs/components/src/navigation/navigation.module.ts @@ -1,13 +1,4 @@ -import { A11yModule } from "@angular/cdk/a11y"; -import { OverlayModule } from "@angular/cdk/overlay"; -import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; -import { RouterModule } from "@angular/router"; - -import { IconModule } from "../icon"; -import { IconButtonModule } from "../icon-button/icon-button.module"; -import { LinkModule } from "../link"; -import { SharedModule } from "../shared/shared.module"; import { NavDividerComponent } from "./nav-divider.component"; import { NavGroupComponent } from "./nav-group.component"; @@ -17,16 +8,6 @@ import { SideNavComponent } from "./side-nav.component"; @NgModule({ imports: [ - CommonModule, - SharedModule, - IconButtonModule, - OverlayModule, - RouterModule, - IconModule, - A11yModule, - LinkModule, - ], - declarations: [ NavDividerComponent, NavGroupComponent, NavItemComponent, diff --git a/libs/components/src/navigation/side-nav.component.ts b/libs/components/src/navigation/side-nav.component.ts index a4af51772b..c86a517100 100644 --- a/libs/components/src/navigation/side-nav.component.ts +++ b/libs/components/src/navigation/side-nav.component.ts @@ -1,7 +1,13 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { CdkTrapFocus } from "@angular/cdk/a11y"; +import { CommonModule } from "@angular/common"; import { Component, ElementRef, Input, ViewChild } from "@angular/core"; +import { BitIconButtonComponent } from "../icon-button/icon-button.component"; +import { I18nPipe } from "../shared/i18n.pipe"; + +import { NavDividerComponent } from "./nav-divider.component"; import { SideNavService } from "./side-nav.service"; export type SideNavVariant = "primary" | "secondary"; @@ -9,6 +15,8 @@ export type SideNavVariant = "primary" | "secondary"; @Component({ selector: "bit-side-nav", templateUrl: "side-nav.component.html", + standalone: true, + imports: [CommonModule, CdkTrapFocus, NavDividerComponent, BitIconButtonComponent, I18nPipe], }) export class SideNavComponent { @Input() variant: SideNavVariant = "primary"; diff --git a/libs/components/src/no-items/no-items.component.ts b/libs/components/src/no-items/no-items.component.ts index d85c6a3457..ee9e0ee058 100644 --- a/libs/components/src/no-items/no-items.component.ts +++ b/libs/components/src/no-items/no-items.component.ts @@ -1,6 +1,7 @@ import { Component, Input } from "@angular/core"; import { Icons } from ".."; +import { BitIconComponent } from "../icon/icon.component"; /** * Component for displaying a message when there are no items to display. Expects title, description and button slots. @@ -8,6 +9,8 @@ import { Icons } from ".."; @Component({ selector: "bit-no-items", templateUrl: "./no-items.component.html", + standalone: true, + imports: [BitIconComponent], }) export class NoItemsComponent { @Input() icon = Icons.Search; diff --git a/libs/components/src/no-items/no-items.module.ts b/libs/components/src/no-items/no-items.module.ts index 9fe6eb37aa..49c3c73f13 100644 --- a/libs/components/src/no-items/no-items.module.ts +++ b/libs/components/src/no-items/no-items.module.ts @@ -1,13 +1,9 @@ -import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; -import { IconModule } from "../icon"; - import { NoItemsComponent } from "./no-items.component"; @NgModule({ - imports: [CommonModule, IconModule], + imports: [NoItemsComponent], exports: [NoItemsComponent], - declarations: [NoItemsComponent], }) export class NoItemsModule {} diff --git a/libs/components/src/progress/progress.component.ts b/libs/components/src/progress/progress.component.ts index 37206dc6ae..04e535158b 100644 --- a/libs/components/src/progress/progress.component.ts +++ b/libs/components/src/progress/progress.component.ts @@ -1,3 +1,4 @@ +import { CommonModule } from "@angular/common"; import { Component, Input } from "@angular/core"; type SizeTypes = "small" | "default" | "large"; @@ -19,6 +20,8 @@ const BackgroundClasses: Record = { @Component({ selector: "bit-progress", templateUrl: "./progress.component.html", + standalone: true, + imports: [CommonModule], }) export class ProgressComponent { @Input() barWidth = 0; diff --git a/libs/components/src/progress/progress.module.ts b/libs/components/src/progress/progress.module.ts index 8ab09189d1..cc93c4c3bd 100644 --- a/libs/components/src/progress/progress.module.ts +++ b/libs/components/src/progress/progress.module.ts @@ -1,11 +1,9 @@ -import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; import { ProgressComponent } from "./progress.component"; @NgModule({ - imports: [CommonModule], + imports: [ProgressComponent], exports: [ProgressComponent], - declarations: [ProgressComponent], }) export class ProgressModule {} diff --git a/libs/components/src/radio-button/radio-button.component.ts b/libs/components/src/radio-button/radio-button.component.ts index dc294103d4..042a54edf4 100644 --- a/libs/components/src/radio-button/radio-button.component.ts +++ b/libs/components/src/radio-button/radio-button.component.ts @@ -1,12 +1,17 @@ import { Component, HostBinding, Input } from "@angular/core"; +import { FormControlModule } from "../form-control/form-control.module"; + import { RadioGroupComponent } from "./radio-group.component"; +import { RadioInputComponent } from "./radio-input.component"; let nextId = 0; @Component({ selector: "bit-radio-button", templateUrl: "radio-button.component.html", + standalone: true, + imports: [FormControlModule, RadioInputComponent], }) export class RadioButtonComponent { @HostBinding("attr.id") @Input() id = `bit-radio-button-${nextId++}`; diff --git a/libs/components/src/radio-button/radio-button.module.ts b/libs/components/src/radio-button/radio-button.module.ts index 21fd942704..7b05c27b4f 100644 --- a/libs/components/src/radio-button/radio-button.module.ts +++ b/libs/components/src/radio-button/radio-button.module.ts @@ -1,16 +1,13 @@ -import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; import { FormControlModule } from "../form-control"; -import { SharedModule } from "../shared"; import { RadioButtonComponent } from "./radio-button.component"; import { RadioGroupComponent } from "./radio-group.component"; import { RadioInputComponent } from "./radio-input.component"; @NgModule({ - imports: [CommonModule, SharedModule, FormControlModule], - declarations: [RadioInputComponent, RadioButtonComponent, RadioGroupComponent], + imports: [FormControlModule, RadioInputComponent, RadioButtonComponent, RadioGroupComponent], exports: [FormControlModule, RadioInputComponent, RadioButtonComponent, RadioGroupComponent], }) export class RadioButtonModule {} diff --git a/libs/components/src/radio-button/radio-group.component.ts b/libs/components/src/radio-button/radio-group.component.ts index 2cddb4fb7b..b9e48f4644 100644 --- a/libs/components/src/radio-button/radio-group.component.ts +++ b/libs/components/src/radio-button/radio-group.component.ts @@ -1,15 +1,19 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { NgIf, NgTemplateOutlet } from "@angular/common"; import { Component, ContentChild, HostBinding, Input, Optional, Self } from "@angular/core"; import { ControlValueAccessor, NgControl, Validators } from "@angular/forms"; import { BitLabel } from "../form-control/label.component"; +import { I18nPipe } from "../shared/i18n.pipe"; let nextId = 0; @Component({ selector: "bit-radio-group", templateUrl: "radio-group.component.html", + standalone: true, + imports: [NgIf, NgTemplateOutlet, I18nPipe], }) export class RadioGroupComponent implements ControlValueAccessor { selected: unknown; diff --git a/libs/components/src/radio-button/radio-input.component.ts b/libs/components/src/radio-button/radio-input.component.ts index 580e5bca25..4a9f5dede6 100644 --- a/libs/components/src/radio-button/radio-input.component.ts +++ b/libs/components/src/radio-button/radio-input.component.ts @@ -11,6 +11,7 @@ let nextId = 0; selector: "input[type=radio][bitRadio]", template: "", providers: [{ provide: BitFormControlAbstraction, useExisting: RadioInputComponent }], + standalone: true, }) export class RadioInputComponent implements BitFormControlAbstraction { @HostBinding("attr.id") @Input() id = `bit-radio-input-${nextId++}`; diff --git a/libs/components/src/search/search.component.ts b/libs/components/src/search/search.component.ts index bc98e5a293..6ec79eaa84 100644 --- a/libs/components/src/search/search.component.ts +++ b/libs/components/src/search/search.component.ts @@ -1,11 +1,18 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { Component, ElementRef, Input, ViewChild } from "@angular/core"; -import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms"; +import { + ControlValueAccessor, + NG_VALUE_ACCESSOR, + ReactiveFormsModule, + FormsModule, +} from "@angular/forms"; import { isBrowserSafariApi } from "@bitwarden/platform"; +import { InputModule } from "../input/input.module"; import { FocusableElement } from "../shared/focusable-element"; +import { I18nPipe } from "../shared/i18n.pipe"; let nextId = 0; @@ -23,6 +30,8 @@ let nextId = 0; useExisting: SearchComponent, }, ], + standalone: true, + imports: [InputModule, ReactiveFormsModule, FormsModule, I18nPipe], }) export class SearchComponent implements ControlValueAccessor, FocusableElement { private notifyOnChange: (v: string) => void; diff --git a/libs/components/src/search/search.module.ts b/libs/components/src/search/search.module.ts index 6207277490..cb9761eae6 100644 --- a/libs/components/src/search/search.module.ts +++ b/libs/components/src/search/search.module.ts @@ -1,14 +1,9 @@ import { NgModule } from "@angular/core"; -import { FormsModule } from "@angular/forms"; - -import { InputModule } from "../input/input.module"; -import { SharedModule } from "../shared"; import { SearchComponent } from "./search.component"; @NgModule({ - imports: [SharedModule, InputModule, FormsModule], - declarations: [SearchComponent], + imports: [SearchComponent], exports: [SearchComponent], }) export class SearchModule {} diff --git a/libs/components/src/select/option.component.ts b/libs/components/src/select/option.component.ts index b32b124be2..841ceda364 100644 --- a/libs/components/src/select/option.component.ts +++ b/libs/components/src/select/option.component.ts @@ -7,6 +7,7 @@ import { Option } from "./option"; @Component({ selector: "bit-option", template: ``, + standalone: true, }) export class OptionComponent implements Option { @Input() diff --git a/libs/components/src/select/select.component.ts b/libs/components/src/select/select.component.ts index cdcf794e48..8f75c5be42 100644 --- a/libs/components/src/select/select.component.ts +++ b/libs/components/src/select/select.component.ts @@ -1,5 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { NgIf } from "@angular/common"; import { Component, ContentChildren, @@ -12,8 +13,14 @@ import { Output, EventEmitter, } from "@angular/core"; -import { ControlValueAccessor, NgControl, Validators } from "@angular/forms"; -import { NgSelectComponent } from "@ng-select/ng-select"; +import { + ControlValueAccessor, + NgControl, + Validators, + ReactiveFormsModule, + FormsModule, +} from "@angular/forms"; +import { NgSelectComponent, NgSelectModule } from "@ng-select/ng-select"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; @@ -28,6 +35,8 @@ let nextId = 0; selector: "bit-select", templateUrl: "select.component.html", providers: [{ provide: BitFormFieldControl, useExisting: SelectComponent }], + standalone: true, + imports: [NgSelectModule, ReactiveFormsModule, FormsModule, NgIf], }) export class SelectComponent implements BitFormFieldControl, ControlValueAccessor { @ViewChild(NgSelectComponent) select: NgSelectComponent; diff --git a/libs/components/src/select/select.module.ts b/libs/components/src/select/select.module.ts index 4391a51817..8807ed63a4 100644 --- a/libs/components/src/select/select.module.ts +++ b/libs/components/src/select/select.module.ts @@ -1,14 +1,10 @@ -import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; -import { FormsModule } from "@angular/forms"; -import { NgSelectModule } from "@ng-select/ng-select"; import { OptionComponent } from "./option.component"; import { SelectComponent } from "./select.component"; @NgModule({ - imports: [CommonModule, NgSelectModule, FormsModule], - declarations: [SelectComponent, OptionComponent], + imports: [SelectComponent, OptionComponent], exports: [SelectComponent, OptionComponent], }) export class SelectModule {} diff --git a/libs/components/src/shared/i18n.pipe.ts b/libs/components/src/shared/i18n.pipe.ts index f428d9297c..91bf0b3198 100644 --- a/libs/components/src/shared/i18n.pipe.ts +++ b/libs/components/src/shared/i18n.pipe.ts @@ -7,6 +7,7 @@ import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.servic */ @Pipe({ name: "i18n", + standalone: true, }) export class I18nPipe implements PipeTransform { constructor(private i18nService: I18nService) {} diff --git a/libs/components/src/shared/shared.module.ts b/libs/components/src/shared/shared.module.ts index dcf2e2bc05..253b049f8f 100644 --- a/libs/components/src/shared/shared.module.ts +++ b/libs/components/src/shared/shared.module.ts @@ -4,8 +4,7 @@ import { NgModule } from "@angular/core"; import { I18nPipe } from "./i18n.pipe"; @NgModule({ - imports: [CommonModule], - declarations: [I18nPipe], + imports: [CommonModule, I18nPipe], exports: [CommonModule, I18nPipe], }) export class SharedModule {} diff --git a/libs/components/src/table/cell.directive.ts b/libs/components/src/table/cell.directive.ts index 61c7557106..8928fe7c09 100644 --- a/libs/components/src/table/cell.directive.ts +++ b/libs/components/src/table/cell.directive.ts @@ -2,6 +2,7 @@ import { Directive, HostBinding } from "@angular/core"; @Directive({ selector: "th[bitCell], td[bitCell]", + standalone: true, }) export class CellDirective { @HostBinding("class") get classList() { diff --git a/libs/components/src/table/row.directive.ts b/libs/components/src/table/row.directive.ts index 19f3d3f775..23347224af 100644 --- a/libs/components/src/table/row.directive.ts +++ b/libs/components/src/table/row.directive.ts @@ -2,6 +2,7 @@ import { Directive, HostBinding, Input } from "@angular/core"; @Directive({ selector: "tr[bitRow]", + standalone: true, }) export class RowDirective { @Input() alignContent: "top" | "middle" | "bottom" | "baseline" = "middle"; diff --git a/libs/components/src/table/sortable.component.ts b/libs/components/src/table/sortable.component.ts index dc3d8dc14f..d3309c03aa 100644 --- a/libs/components/src/table/sortable.component.ts +++ b/libs/components/src/table/sortable.component.ts @@ -1,6 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { coerceBooleanProperty } from "@angular/cdk/coercion"; +import { NgClass } from "@angular/common"; import { Component, HostBinding, Input, OnInit } from "@angular/core"; import type { SortDirection, SortFn } from "./table-data-source"; @@ -14,6 +15,8 @@ import { TableComponent } from "./table.component"; `, + standalone: true, + imports: [NgClass], }) export class SortableComponent implements OnInit { /** diff --git a/libs/components/src/table/table-scroll.component.ts b/libs/components/src/table/table-scroll.component.ts index 9e308b7da5..34cd8c5d9c 100644 --- a/libs/components/src/table/table-scroll.component.ts +++ b/libs/components/src/table/table-scroll.component.ts @@ -1,5 +1,12 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { + CdkVirtualScrollViewport, + CdkVirtualScrollableWindow, + CdkFixedSizeVirtualScroll, + CdkVirtualForOf, +} from "@angular/cdk/scrolling"; +import { CommonModule } from "@angular/common"; import { AfterContentChecked, Component, @@ -14,6 +21,7 @@ import { TrackByFunction, } from "@angular/core"; +import { RowDirective } from "./row.directive"; import { TableComponent } from "./table.component"; /** @@ -42,6 +50,15 @@ export class BitRowDef { selector: "bit-table-scroll", templateUrl: "./table-scroll.component.html", providers: [{ provide: TableComponent, useExisting: TableScrollComponent }], + standalone: true, + imports: [ + CommonModule, + CdkVirtualScrollViewport, + CdkVirtualScrollableWindow, + CdkFixedSizeVirtualScroll, + CdkVirtualForOf, + RowDirective, + ], }) export class TableScrollComponent extends TableComponent diff --git a/libs/components/src/table/table.component.ts b/libs/components/src/table/table.component.ts index 8bc7754b16..cd0a2a6c65 100644 --- a/libs/components/src/table/table.component.ts +++ b/libs/components/src/table/table.component.ts @@ -1,6 +1,7 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore import { isDataSource } from "@angular/cdk/collections"; +import { CommonModule } from "@angular/common"; import { AfterContentChecked, Component, @@ -16,6 +17,7 @@ import { TableDataSource } from "./table-data-source"; @Directive({ selector: "ng-template[body]", + standalone: true, }) export class TableBodyDirective { // eslint-disable-next-line @typescript-eslint/explicit-member-accessibility @@ -25,6 +27,8 @@ export class TableBodyDirective { @Component({ selector: "bit-table", templateUrl: "./table.component.html", + standalone: true, + imports: [CommonModule], }) export class TableComponent implements OnDestroy, AfterContentChecked { @Input() dataSource: TableDataSource; diff --git a/libs/components/src/table/table.module.ts b/libs/components/src/table/table.module.ts index 1f1b705c69..6899361277 100644 --- a/libs/components/src/table/table.module.ts +++ b/libs/components/src/table/table.module.ts @@ -9,8 +9,10 @@ import { BitRowDef, TableScrollComponent } from "./table-scroll.component"; import { TableBodyDirective, TableComponent } from "./table.component"; @NgModule({ - imports: [CommonModule, ScrollingModule, BitRowDef], - declarations: [ + imports: [ + CommonModule, + ScrollingModule, + BitRowDef, CellDirective, RowDirective, SortableComponent, diff --git a/libs/components/src/tabs/shared/tab-header.component.ts b/libs/components/src/tabs/shared/tab-header.component.ts index 4712df0549..c45bafb3d5 100644 --- a/libs/components/src/tabs/shared/tab-header.component.ts +++ b/libs/components/src/tabs/shared/tab-header.component.ts @@ -10,5 +10,6 @@ import { Component } from "@angular/core"; "tw-h-16 tw-pl-4 tw-bg-background-alt tw-flex tw-items-end tw-border-0 tw-border-b tw-border-solid tw-border-secondary-300", }, template: ``, + standalone: true, }) export class TabHeaderComponent {} diff --git a/libs/components/src/tabs/shared/tab-list-container.directive.ts b/libs/components/src/tabs/shared/tab-list-container.directive.ts index 1cf8a762d5..cedae44e58 100644 --- a/libs/components/src/tabs/shared/tab-list-container.directive.ts +++ b/libs/components/src/tabs/shared/tab-list-container.directive.ts @@ -8,5 +8,6 @@ import { Directive } from "@angular/core"; host: { class: "tw-inline-flex tw-flex-wrap tw-leading-5", }, + standalone: true, }) export class TabListContainerDirective {} diff --git a/libs/components/src/tabs/shared/tab-list-item.directive.ts b/libs/components/src/tabs/shared/tab-list-item.directive.ts index 7514f5417e..87435133a2 100644 --- a/libs/components/src/tabs/shared/tab-list-item.directive.ts +++ b/libs/components/src/tabs/shared/tab-list-item.directive.ts @@ -7,7 +7,10 @@ import { Directive, ElementRef, HostBinding, Input } from "@angular/core"; * Directive used for styling tab header items for both nav links (anchor tags) * and content tabs (button tags) */ -@Directive({ selector: "[bitTabListItem]" }) +@Directive({ + selector: "[bitTabListItem]", + standalone: true, +}) export class TabListItemDirective implements FocusableOption { @Input() active: boolean; @Input() disabled: boolean; diff --git a/libs/components/src/tabs/tab-group/tab-body.component.ts b/libs/components/src/tabs/tab-group/tab-body.component.ts index 7cb6664b7c..45a6a05e7c 100644 --- a/libs/components/src/tabs/tab-group/tab-body.component.ts +++ b/libs/components/src/tabs/tab-group/tab-body.component.ts @@ -1,11 +1,13 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore -import { TemplatePortal } from "@angular/cdk/portal"; +import { TemplatePortal, CdkPortalOutlet } from "@angular/cdk/portal"; import { Component, HostBinding, Input } from "@angular/core"; @Component({ selector: "bit-tab-body", templateUrl: "tab-body.component.html", + standalone: true, + imports: [CdkPortalOutlet], }) export class TabBodyComponent { private _firstRender: boolean; diff --git a/libs/components/src/tabs/tab-group/tab-group.component.ts b/libs/components/src/tabs/tab-group/tab-group.component.ts index 7b0cb60bb1..54d00343b3 100644 --- a/libs/components/src/tabs/tab-group/tab-group.component.ts +++ b/libs/components/src/tabs/tab-group/tab-group.component.ts @@ -2,6 +2,7 @@ // @ts-strict-ignore import { FocusKeyManager } from "@angular/cdk/a11y"; import { coerceNumberProperty } from "@angular/cdk/coercion"; +import { CommonModule } from "@angular/common"; import { AfterContentChecked, AfterContentInit, @@ -17,8 +18,11 @@ import { } from "@angular/core"; import { Subject, takeUntil } from "rxjs"; +import { TabHeaderComponent } from "../shared/tab-header.component"; +import { TabListContainerDirective } from "../shared/tab-list-container.directive"; import { TabListItemDirective } from "../shared/tab-list-item.directive"; +import { TabBodyComponent } from "./tab-body.component"; import { TabComponent } from "./tab.component"; /** Used to generate unique ID's for each tab component */ @@ -27,6 +31,14 @@ let nextId = 0; @Component({ selector: "bit-tab-group", templateUrl: "./tab-group.component.html", + standalone: true, + imports: [ + CommonModule, + TabHeaderComponent, + TabListContainerDirective, + TabListItemDirective, + TabBodyComponent, + ], }) export class TabGroupComponent implements AfterContentChecked, AfterContentInit, AfterViewInit, OnDestroy diff --git a/libs/components/src/tabs/tab-group/tab-label.directive.ts b/libs/components/src/tabs/tab-group/tab-label.directive.ts index 45da163631..9a0e59845a 100644 --- a/libs/components/src/tabs/tab-group/tab-label.directive.ts +++ b/libs/components/src/tabs/tab-group/tab-label.directive.ts @@ -16,6 +16,7 @@ import { Directive, TemplateRef } from "@angular/core"; */ @Directive({ selector: "[bitTabLabel]", + standalone: true, }) export class TabLabelDirective { constructor(public templateRef: TemplateRef) {} diff --git a/libs/components/src/tabs/tab-group/tab.component.ts b/libs/components/src/tabs/tab-group/tab.component.ts index 260cb0c819..b2c9b1999b 100644 --- a/libs/components/src/tabs/tab-group/tab.component.ts +++ b/libs/components/src/tabs/tab-group/tab.component.ts @@ -19,6 +19,7 @@ import { TabLabelDirective } from "./tab-label.directive"; host: { role: "tabpanel", }, + standalone: true, }) export class TabComponent implements OnInit { @Input() disabled = false; diff --git a/libs/components/src/tabs/tab-nav-bar/tab-link.component.ts b/libs/components/src/tabs/tab-nav-bar/tab-link.component.ts index 483aa9600b..0dac668147 100644 --- a/libs/components/src/tabs/tab-nav-bar/tab-link.component.ts +++ b/libs/components/src/tabs/tab-nav-bar/tab-link.component.ts @@ -2,7 +2,7 @@ // @ts-strict-ignore import { FocusableOption } from "@angular/cdk/a11y"; import { AfterViewInit, Component, HostListener, Input, OnDestroy, ViewChild } from "@angular/core"; -import { IsActiveMatchOptions, RouterLinkActive } from "@angular/router"; +import { IsActiveMatchOptions, RouterLinkActive, RouterModule } from "@angular/router"; import { Subject, takeUntil } from "rxjs"; import { TabListItemDirective } from "../shared/tab-list-item.directive"; @@ -12,6 +12,8 @@ import { TabNavBarComponent } from "./tab-nav-bar.component"; @Component({ selector: "bit-tab-link", templateUrl: "tab-link.component.html", + standalone: true, + imports: [TabListItemDirective, RouterModule], }) export class TabLinkComponent implements FocusableOption, AfterViewInit, OnDestroy { private destroy$ = new Subject(); diff --git a/libs/components/src/tabs/tab-nav-bar/tab-nav-bar.component.ts b/libs/components/src/tabs/tab-nav-bar/tab-nav-bar.component.ts index 81f7f1d494..305196a0c6 100644 --- a/libs/components/src/tabs/tab-nav-bar/tab-nav-bar.component.ts +++ b/libs/components/src/tabs/tab-nav-bar/tab-nav-bar.component.ts @@ -10,6 +10,9 @@ import { QueryList, } from "@angular/core"; +import { TabHeaderComponent } from "../shared/tab-header.component"; +import { TabListContainerDirective } from "../shared/tab-list-container.directive"; + import { TabLinkComponent } from "./tab-link.component"; @Component({ @@ -18,6 +21,8 @@ import { TabLinkComponent } from "./tab-link.component"; host: { class: "tw-block", }, + standalone: true, + imports: [TabHeaderComponent, TabListContainerDirective], }) export class TabNavBarComponent implements AfterContentInit { @ContentChildren(forwardRef(() => TabLinkComponent)) tabLabels: QueryList; diff --git a/libs/components/src/tabs/tabs.module.ts b/libs/components/src/tabs/tabs.module.ts index fee1a8a7d0..ef1537db67 100644 --- a/libs/components/src/tabs/tabs.module.ts +++ b/libs/components/src/tabs/tabs.module.ts @@ -1,11 +1,6 @@ -import { PortalModule } from "@angular/cdk/portal"; import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; -import { RouterModule } from "@angular/router"; -import { TabHeaderComponent } from "./shared/tab-header.component"; -import { TabListContainerDirective } from "./shared/tab-list-container.directive"; -import { TabListItemDirective } from "./shared/tab-list-item.directive"; import { TabBodyComponent } from "./tab-group/tab-body.component"; import { TabGroupComponent } from "./tab-group/tab-group.component"; import { TabLabelDirective } from "./tab-group/tab-label.directive"; @@ -14,7 +9,15 @@ import { TabLinkComponent } from "./tab-nav-bar/tab-link.component"; import { TabNavBarComponent } from "./tab-nav-bar/tab-nav-bar.component"; @NgModule({ - imports: [CommonModule, RouterModule, PortalModule], + imports: [ + CommonModule, + TabGroupComponent, + TabComponent, + TabLabelDirective, + TabNavBarComponent, + TabLinkComponent, + TabBodyComponent, + ], exports: [ TabGroupComponent, TabComponent, @@ -22,16 +25,5 @@ import { TabNavBarComponent } from "./tab-nav-bar/tab-nav-bar.component"; TabNavBarComponent, TabLinkComponent, ], - declarations: [ - TabGroupComponent, - TabComponent, - TabLabelDirective, - TabListContainerDirective, - TabListItemDirective, - TabHeaderComponent, - TabNavBarComponent, - TabLinkComponent, - TabBodyComponent, - ], }) export class TabsModule {} diff --git a/libs/components/src/toast/toast.module.ts b/libs/components/src/toast/toast.module.ts index bf39a0be9a..bf17fde223 100644 --- a/libs/components/src/toast/toast.module.ts +++ b/libs/components/src/toast/toast.module.ts @@ -1,13 +1,10 @@ -import { CommonModule } from "@angular/common"; import { ModuleWithProviders, NgModule } from "@angular/core"; import { DefaultNoComponentGlobalConfig, GlobalConfig, TOAST_CONFIG } from "ngx-toastr"; -import { ToastComponent } from "./toast.component"; import { BitwardenToastrComponent } from "./toastr.component"; @NgModule({ - imports: [CommonModule, ToastComponent], - declarations: [BitwardenToastrComponent], + imports: [BitwardenToastrComponent], exports: [BitwardenToastrComponent], }) export class ToastModule { diff --git a/libs/components/src/toast/toastr.component.ts b/libs/components/src/toast/toastr.component.ts index 0656b68d86..2420905494 100644 --- a/libs/components/src/toast/toastr.component.ts +++ b/libs/components/src/toast/toastr.component.ts @@ -2,6 +2,8 @@ import { animate, state, style, transition, trigger } from "@angular/animations" import { Component } from "@angular/core"; import { Toast as BaseToastrComponent } from "ngx-toastr"; +import { ToastComponent } from "./toast.component"; + @Component({ template: ` { private id = nextId++; diff --git a/libs/components/src/toggle-group/toggle-group.module.ts b/libs/components/src/toggle-group/toggle-group.module.ts index fe1ce0ec52..654149611f 100644 --- a/libs/components/src/toggle-group/toggle-group.module.ts +++ b/libs/components/src/toggle-group/toggle-group.module.ts @@ -1,14 +1,10 @@ -import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; -import { BadgeModule } from "../badge"; - import { ToggleGroupComponent } from "./toggle-group.component"; import { ToggleComponent } from "./toggle.component"; @NgModule({ - imports: [CommonModule, BadgeModule], + imports: [ToggleGroupComponent, ToggleComponent], exports: [ToggleGroupComponent, ToggleComponent], - declarations: [ToggleGroupComponent, ToggleComponent], }) export class ToggleGroupModule {} diff --git a/libs/components/src/toggle-group/toggle.component.ts b/libs/components/src/toggle-group/toggle.component.ts index c7d9dc5bf3..7bd6205676 100644 --- a/libs/components/src/toggle-group/toggle.component.ts +++ b/libs/components/src/toggle-group/toggle.component.ts @@ -1,5 +1,6 @@ // FIXME: Update this file to be type safe and remove this and next line // @ts-strict-ignore +import { NgClass } from "@angular/common"; import { AfterContentChecked, AfterViewInit, @@ -19,6 +20,8 @@ let nextId = 0; selector: "bit-toggle", templateUrl: "./toggle.component.html", preserveWhitespaces: false, + standalone: true, + imports: [NgClass], }) export class ToggleComponent implements AfterContentChecked, AfterViewInit { id = nextId++; diff --git a/libs/components/src/typography/typography.directive.ts b/libs/components/src/typography/typography.directive.ts index e48ef67001..36d6b996db 100644 --- a/libs/components/src/typography/typography.directive.ts +++ b/libs/components/src/typography/typography.directive.ts @@ -31,6 +31,7 @@ const margins: Record = { @Directive({ selector: "[bitTypography]", + standalone: true, }) export class TypographyDirective { @Input("bitTypography") bitTypography: TypographyType; diff --git a/libs/components/src/typography/typography.module.ts b/libs/components/src/typography/typography.module.ts index 7ee6690636..74d1d4d6e6 100644 --- a/libs/components/src/typography/typography.module.ts +++ b/libs/components/src/typography/typography.module.ts @@ -1,11 +1,9 @@ -import { CommonModule } from "@angular/common"; import { NgModule } from "@angular/core"; import { TypographyDirective } from "./typography.directive"; @NgModule({ - imports: [CommonModule], + imports: [TypographyDirective], exports: [TypographyDirective], - declarations: [TypographyDirective], }) export class TypographyModule {} From 903b5c8d93bb3d19d5960f0eb60ca20b7d604c05 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann