From fe4895d97e64ba54efb4972399b9583dc774bd3b Mon Sep 17 00:00:00 2001 From: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> Date: Wed, 10 Dec 2025 15:24:20 -0600 Subject: [PATCH] [PM-28264] Consolidate and update the UI for key connector migration/confirmation (#17642) * Consolidate the RemovePasswordComponent * Add getting confirmation details for confirm key connector * Add missing message --- apps/browser/src/_locales/en/messages.json | 42 +++++++++- .../remove-password.component.html | 51 ------------ .../remove-password.component.ts | 14 ---- apps/browser/src/popup/app-routing.module.ts | 24 +++++- apps/browser/src/popup/app.module.ts | 9 +-- apps/desktop/src/app/app-routing.module.ts | 25 ++++-- apps/desktop/src/app/app.module.ts | 2 - .../remove-password.component.html | 20 ----- .../remove-password.component.ts | 12 --- apps/desktop/src/locales/en/messages.json | 42 +++++++++- .../remove-password.component.html | 37 --------- .../remove-password.component.ts | 12 --- apps/web/src/app/oss-routing.module.ts | 11 ++- .../src/app/shared/loose-components.module.ts | 3 - apps/web/src/locales/en/messages.json | 42 +++++++++- .../src/services/jslib-services.module.ts | 7 ++ .../two-factor-auth.component.spec.ts | 1 + .../abstractions/key-connector-api.service.ts | 7 ++ .../key-connector-domain-confirmation.ts | 1 + ...connector-confirmation-details.response.ts | 10 +++ .../default-key-connector-api.service.spec.ts | 54 +++++++++++++ .../default-key-connector-api.service.ts | 20 +++++ .../services/key-connector.service.spec.ts | 5 +- .../services/key-connector.service.ts | 13 ++- ...onfirm-key-connector-domain.component.html | 29 +++++-- ...irm-key-connector-domain.component.spec.ts | 79 ++++++++++++++++++- .../confirm-key-connector-domain.component.ts | 61 +++++++++++++- .../remove-password.component.html | 35 ++++++++ .../remove-password.component.spec.ts | 2 + .../remove-password.component.ts | 32 ++++++-- 30 files changed, 496 insertions(+), 206 deletions(-) delete mode 100644 apps/browser/src/key-management/key-connector/remove-password.component.html delete mode 100644 apps/browser/src/key-management/key-connector/remove-password.component.ts delete mode 100644 apps/desktop/src/key-management/key-connector/remove-password.component.html delete mode 100644 apps/desktop/src/key-management/key-connector/remove-password.component.ts delete mode 100644 apps/web/src/app/key-management/key-connector/remove-password.component.html delete mode 100644 apps/web/src/app/key-management/key-connector/remove-password.component.ts create mode 100644 libs/common/src/key-management/key-connector/abstractions/key-connector-api.service.ts create mode 100644 libs/common/src/key-management/key-connector/models/response/key-connector-confirmation-details.response.ts create mode 100644 libs/common/src/key-management/key-connector/services/default-key-connector-api.service.spec.ts create mode 100644 libs/common/src/key-management/key-connector/services/default-key-connector-api.service.ts create mode 100644 libs/key-management-ui/src/key-connector/remove-password.component.html diff --git a/apps/browser/src/_locales/en/messages.json b/apps/browser/src/_locales/en/messages.json index a90fbcbf332..09ea964823c 100644 --- a/apps/browser/src/_locales/en/messages.json +++ b/apps/browser/src/_locales/en/messages.json @@ -3252,9 +3252,6 @@ "copyCustomFieldNameNotUnique": { "message": "No unique identifier found." }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -5891,6 +5888,45 @@ "cardNumberLabel": { "message": "Card number" }, + "removeMasterPasswordForOrgUserKeyConnector":{ + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified":{ + "message": "Organization verified" + }, + "domainVerified":{ + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, diff --git a/apps/browser/src/key-management/key-connector/remove-password.component.html b/apps/browser/src/key-management/key-connector/remove-password.component.html deleted file mode 100644 index 427065e83f3..00000000000 --- a/apps/browser/src/key-management/key-connector/remove-password.component.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - @if (loading) { -
- - {{ "loading" | i18n }} -
- } @else { -

{{ "removeMasterPasswordForOrganizationUserKeyConnector" | i18n }}

-

{{ "organizationName" | i18n }}:

-

{{ organization.name }}

-

{{ "keyConnectorDomain" | i18n }}:

-

{{ organization.keyConnectorUrl }}

- - - - } -
diff --git a/apps/browser/src/key-management/key-connector/remove-password.component.ts b/apps/browser/src/key-management/key-connector/remove-password.component.ts deleted file mode 100644 index c4077a1eca9..00000000000 --- a/apps/browser/src/key-management/key-connector/remove-password.component.ts +++ /dev/null @@ -1,14 +0,0 @@ -// FIXME (PM-22628): angular imports are forbidden in background -// eslint-disable-next-line no-restricted-imports -import { Component } from "@angular/core"; - -import { RemovePasswordComponent as BaseRemovePasswordComponent } from "@bitwarden/key-management-ui"; - -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -@Component({ - selector: "app-remove-password", - templateUrl: "remove-password.component.html", - standalone: false, -}) -export class RemovePasswordComponent extends BaseRemovePasswordComponent {} diff --git a/apps/browser/src/popup/app-routing.module.ts b/apps/browser/src/popup/app-routing.module.ts index 48f06147cdf..eb64c076192 100644 --- a/apps/browser/src/popup/app-routing.module.ts +++ b/apps/browser/src/popup/app-routing.module.ts @@ -43,7 +43,11 @@ import { TwoFactorAuthGuard, } from "@bitwarden/auth/angular"; import { AnonLayoutWrapperComponent, AnonLayoutWrapperData } from "@bitwarden/components"; -import { LockComponent, ConfirmKeyConnectorDomainComponent } from "@bitwarden/key-management-ui"; +import { + LockComponent, + ConfirmKeyConnectorDomainComponent, + RemovePasswordComponent, +} from "@bitwarden/key-management-ui"; import { AccountSwitcherComponent } from "../auth/popup/account-switching/account-switcher.component"; import { AuthExtensionRoute } from "../auth/popup/constants/auth-extension-route.constant"; @@ -59,7 +63,6 @@ import { NotificationsSettingsComponent } from "../autofill/popup/settings/notif import { PremiumV2Component } from "../billing/popup/settings/premium-v2.component"; import { PhishingWarning } from "../dirt/phishing-detection/popup/phishing-warning.component"; import { ProtectedByComponent } from "../dirt/phishing-detection/popup/protected-by-component"; -import { RemovePasswordComponent } from "../key-management/key-connector/remove-password.component"; import BrowserPopupUtils from "../platform/browser/browser-popup-utils"; import { popupRouterCacheGuard } from "../platform/popup/view-cache/popup-router-cache.service"; import { RouteCacheOptions } from "../platform/services/popup-view-cache-background.service"; @@ -188,9 +191,22 @@ const routes: Routes = [ }, { path: "remove-password", - component: RemovePasswordComponent, + component: ExtensionAnonLayoutWrapperComponent, canActivate: [authGuard], data: { elevation: 1 } satisfies RouteDataProperties, + children: [ + { + path: "", + component: RemovePasswordComponent, + data: { + pageTitle: { + key: "verifyYourOrganization", + }, + showBackButton: false, + pageIcon: LockIcon, + } satisfies ExtensionAnonLayoutWrapperData, + }, + ], }, { path: "view-cipher", @@ -646,7 +662,7 @@ const routes: Routes = [ component: ConfirmKeyConnectorDomainComponent, data: { pageTitle: { - key: "confirmKeyConnectorDomain", + key: "verifyYourOrganization", }, showBackButton: true, pageIcon: DomainIcon, diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts index 71846cc6444..d178cee2fc3 100644 --- a/apps/browser/src/popup/app.module.ts +++ b/apps/browser/src/popup/app.module.ts @@ -28,7 +28,6 @@ import { CurrentAccountComponent } from "../auth/popup/account-switching/current import { AccountSecurityComponent } from "../auth/popup/settings/account-security.component"; import { AutofillComponent } from "../autofill/popup/settings/autofill.component"; import { NotificationsSettingsComponent } from "../autofill/popup/settings/notifications.component"; -import { RemovePasswordComponent } from "../key-management/key-connector/remove-password.component"; import { PopOutComponent } from "../platform/popup/components/pop-out.component"; import { PopupFooterComponent } from "../platform/popup/layout/popup-footer.component"; import { PopupHeaderComponent } from "../platform/popup/layout/popup-header.component"; @@ -85,13 +84,7 @@ import "../platform/popup/locales"; CalloutModule, LinkModule, ], - declarations: [ - AppComponent, - ColorPasswordPipe, - ColorPasswordCountPipe, - TabsV2Component, - RemovePasswordComponent, - ], + declarations: [AppComponent, ColorPasswordPipe, ColorPasswordCountPipe, TabsV2Component], exports: [CalloutModule], providers: [CurrencyPipe, DatePipe], bootstrap: [AppComponent], diff --git a/apps/desktop/src/app/app-routing.module.ts b/apps/desktop/src/app/app-routing.module.ts index fdc421153e1..6077afa8c12 100644 --- a/apps/desktop/src/app/app-routing.module.ts +++ b/apps/desktop/src/app/app-routing.module.ts @@ -42,14 +42,17 @@ import { } from "@bitwarden/auth/angular"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { AnonLayoutWrapperComponent, AnonLayoutWrapperData } from "@bitwarden/components"; -import { LockComponent, ConfirmKeyConnectorDomainComponent } from "@bitwarden/key-management-ui"; +import { + LockComponent, + ConfirmKeyConnectorDomainComponent, + RemovePasswordComponent, +} from "@bitwarden/key-management-ui"; import { maxAccountsGuardFn } from "../auth/guards/max-accounts.guard"; import { reactiveUnlockVaultGuard } from "../autofill/guards/reactive-vault-guard"; import { Fido2CreateComponent } from "../autofill/modal/credentials/fido2-create.component"; import { Fido2ExcludedCiphersComponent } from "../autofill/modal/credentials/fido2-excluded-ciphers.component"; import { Fido2VaultComponent } from "../autofill/modal/credentials/fido2-vault.component"; -import { RemovePasswordComponent } from "../key-management/key-connector/remove-password.component"; import { VaultV2Component } from "../vault/app/vault/vault-v2.component"; import { VaultComponent } from "../vault/app/vault-v3/vault.component"; @@ -117,11 +120,6 @@ const routes: Routes = [ component: SendComponent, canActivate: [authGuard], }, - { - path: "remove-password", - component: RemovePasswordComponent, - canActivate: [authGuard], - }, { path: "fido2-assertion", component: Fido2VaultComponent, @@ -327,13 +325,24 @@ const routes: Routes = [ pageIcon: LockIcon, } satisfies AnonLayoutWrapperData, }, + { + path: "remove-password", + component: RemovePasswordComponent, + canActivate: [authGuard], + data: { + pageTitle: { + key: "verifyYourOrganization", + }, + pageIcon: LockIcon, + } satisfies RouteDataProperties & AnonLayoutWrapperData, + }, { path: "confirm-key-connector-domain", component: ConfirmKeyConnectorDomainComponent, canActivate: [], data: { pageTitle: { - key: "confirmKeyConnectorDomain", + key: "verifyYourOrganization", }, pageIcon: DomainIcon, } satisfies RouteDataProperties & AnonLayoutWrapperData, diff --git a/apps/desktop/src/app/app.module.ts b/apps/desktop/src/app/app.module.ts index 4f53e587994..31131c6202a 100644 --- a/apps/desktop/src/app/app.module.ts +++ b/apps/desktop/src/app/app.module.ts @@ -15,7 +15,6 @@ import { DeleteAccountComponent } from "../auth/delete-account.component"; import { LoginModule } from "../auth/login/login.module"; import { SshAgentService } from "../autofill/services/ssh-agent.service"; import { PremiumComponent } from "../billing/app/accounts/premium.component"; -import { RemovePasswordComponent } from "../key-management/key-connector/remove-password.component"; import { VaultFilterModule } from "../vault/app/vault/vault-filter/vault-filter.module"; import { VaultV2Component } from "../vault/app/vault/vault-v2.component"; @@ -50,7 +49,6 @@ import { SharedModule } from "./shared/shared.module"; ColorPasswordCountPipe, HeaderComponent, PremiumComponent, - RemovePasswordComponent, SearchComponent, ], providers: [SshAgentService], diff --git a/apps/desktop/src/key-management/key-connector/remove-password.component.html b/apps/desktop/src/key-management/key-connector/remove-password.component.html deleted file mode 100644 index 5276e00c531..00000000000 --- a/apps/desktop/src/key-management/key-connector/remove-password.component.html +++ /dev/null @@ -1,20 +0,0 @@ -
-
-

{{ "removeMasterPassword" | i18n }}

-

{{ "removeMasterPasswordForOrganizationUserKeyConnector" | i18n }}

-

{{ "organizationName" | i18n }}:

-

{{ organization.name }}

-

{{ "keyConnectorDomain" | i18n }}:

-

{{ organization.keyConnectorUrl }}

-
- - -
-
-
diff --git a/apps/desktop/src/key-management/key-connector/remove-password.component.ts b/apps/desktop/src/key-management/key-connector/remove-password.component.ts deleted file mode 100644 index d9fea9409f8..00000000000 --- a/apps/desktop/src/key-management/key-connector/remove-password.component.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component } from "@angular/core"; - -import { RemovePasswordComponent as BaseRemovePasswordComponent } from "@bitwarden/key-management-ui"; - -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -@Component({ - selector: "app-remove-password", - templateUrl: "remove-password.component.html", - standalone: false, -}) -export class RemovePasswordComponent extends BaseRemovePasswordComponent {} diff --git a/apps/desktop/src/locales/en/messages.json b/apps/desktop/src/locales/en/messages.json index 7a3abe528e8..48e346d9c68 100644 --- a/apps/desktop/src/locales/en/messages.json +++ b/apps/desktop/src/locales/en/messages.json @@ -2637,9 +2637,6 @@ "removedMasterPassword": { "message": "Master password removed" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "organizationName": { "message": "Organization name" }, @@ -4337,6 +4334,45 @@ "upgradeToPremium": { "message": "Upgrade to Premium" }, + "removeMasterPasswordForOrgUserKeyConnector":{ + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified":{ + "message": "Organization verified" + }, + "domainVerified":{ + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "sessionTimeoutSettingsAction": { "message": "Timeout action" }, diff --git a/apps/web/src/app/key-management/key-connector/remove-password.component.html b/apps/web/src/app/key-management/key-connector/remove-password.component.html deleted file mode 100644 index aae660ce504..00000000000 --- a/apps/web/src/app/key-management/key-connector/remove-password.component.html +++ /dev/null @@ -1,37 +0,0 @@ -
- - {{ "loading" | i18n }} -
- -
-

{{ "removeMasterPasswordForOrganizationUserKeyConnector" | i18n }}

-

{{ "organizationName" | i18n }}:

-

{{ organization.name }}

-

{{ "keyConnectorDomain" | i18n }}:

-

{{ organization.keyConnectorUrl }}

- - - -
diff --git a/apps/web/src/app/key-management/key-connector/remove-password.component.ts b/apps/web/src/app/key-management/key-connector/remove-password.component.ts deleted file mode 100644 index d9fea9409f8..00000000000 --- a/apps/web/src/app/key-management/key-connector/remove-password.component.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component } from "@angular/core"; - -import { RemovePasswordComponent as BaseRemovePasswordComponent } from "@bitwarden/key-management-ui"; - -// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush -// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection -@Component({ - selector: "app-remove-password", - templateUrl: "remove-password.component.html", - standalone: false, -}) -export class RemovePasswordComponent extends BaseRemovePasswordComponent {} diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index ac9bdc4b946..e3c9da635f9 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -51,7 +51,7 @@ import { import { canAccessEmergencyAccess } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction"; import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; import { AnonLayoutWrapperComponent, AnonLayoutWrapperData } from "@bitwarden/components"; -import { LockComponent } from "@bitwarden/key-management-ui"; +import { LockComponent, RemovePasswordComponent } from "@bitwarden/key-management-ui"; import { premiumInterestRedirectGuard } from "@bitwarden/web-vault/app/vault/guards/premium-interest-redirect/premium-interest-redirect.guard"; import { flagEnabled, Flags } from "../utils/flags"; @@ -80,7 +80,6 @@ import { RouteDataProperties } from "./core"; import { ReportsModule } from "./dirt/reports"; import { DataRecoveryComponent } from "./key-management/data-recovery/data-recovery.component"; import { ConfirmKeyConnectorDomainComponent } from "./key-management/key-connector/confirm-key-connector-domain.component"; -import { RemovePasswordComponent } from "./key-management/key-connector/remove-password.component"; import { FrontendLayoutComponent } from "./layouts/frontend-layout.component"; import { UserLayoutComponent } from "./layouts/user-layout.component"; import { RequestSMAccessComponent } from "./secrets-manager/secrets-manager-landing/request-sm-access.component"; @@ -545,9 +544,9 @@ const routes: Routes = [ canActivate: [authGuard], data: { pageTitle: { - key: "removeMasterPassword", + key: "verifyYourOrganization", }, - titleId: "removeMasterPassword", + titleId: "verifyYourOrganization", pageIcon: LockIcon, } satisfies RouteDataProperties & AnonLayoutWrapperData, }, @@ -557,9 +556,9 @@ const routes: Routes = [ canActivate: [], data: { pageTitle: { - key: "confirmKeyConnectorDomain", + key: "verifyYourOrganization", }, - titleId: "confirmKeyConnectorDomain", + titleId: "verifyYourOrganization", pageIcon: DomainIcon, } satisfies RouteDataProperties & AnonLayoutWrapperData, }, diff --git a/apps/web/src/app/shared/loose-components.module.ts b/apps/web/src/app/shared/loose-components.module.ts index 0fff13f428c..f096ef6a292 100644 --- a/apps/web/src/app/shared/loose-components.module.ts +++ b/apps/web/src/app/shared/loose-components.module.ts @@ -7,7 +7,6 @@ import { VerifyRecoverDeleteComponent } from "../auth/verify-recover-delete.comp import { FreeBitwardenFamiliesComponent } from "../billing/members/free-bitwarden-families.component"; import { SponsoredFamiliesComponent } from "../billing/settings/sponsored-families.component"; import { SponsoringOrgRowComponent } from "../billing/settings/sponsoring-org-row.component"; -import { RemovePasswordComponent } from "../key-management/key-connector/remove-password.component"; import { HeaderModule } from "../layouts/header/header.module"; import { OrganizationBadgeModule } from "../vault/individual-vault/organization-badge/organization-badge.module"; import { PipesModule } from "../vault/individual-vault/pipes/pipes.module"; @@ -21,7 +20,6 @@ import { SharedModule } from "./shared.module"; declarations: [ RecoverDeleteComponent, RecoverTwoFactorComponent, - RemovePasswordComponent, SponsoredFamiliesComponent, FreeBitwardenFamiliesComponent, SponsoringOrgRowComponent, @@ -31,7 +29,6 @@ import { SharedModule } from "./shared.module"; exports: [ RecoverDeleteComponent, RecoverTwoFactorComponent, - RemovePasswordComponent, SponsoredFamiliesComponent, VerifyEmailTokenComponent, VerifyRecoverDeleteComponent, diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index be2f72e34b0..3b0554547c5 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -7136,9 +7136,6 @@ "invalidVerificationCode": { "message": "Invalid verification code" }, - "removeMasterPasswordForOrganizationUserKeyConnector": { - "message": "A master password is no longer required for members of the following organization. Please confirm the domain below with your organization administrator." - }, "keyConnectorDomain": { "message": "Key Connector domain" }, @@ -12247,6 +12244,45 @@ } } }, + "removeMasterPasswordForOrgUserKeyConnector":{ + "message": "Your organization is no longer using master passwords to log into Bitwarden. To continue, verify the organization and domain." + }, + "continueWithLogIn": { + "message": "Continue with log in" + }, + "doNotContinue": { + "message": "Do not continue" + }, + "domain": { + "message": "Domain" + }, + "keyConnectorDomainTooltip": { + "message": "This domain will store your account encryption keys, so make sure you trust it. If you're not sure, check with your admin." + }, + "verifyYourOrganization": { + "message": "Verify your organization to log in" + }, + "organizationVerified":{ + "message": "Organization verified" + }, + "domainVerified":{ + "message": "Domain verified" + }, + "leaveOrganizationContent": { + "message": "If you don't verify your organization, your access to the organization will be revoked." + }, + "leaveNow": { + "message": "Leave now" + }, + "verifyYourDomainToLogin": { + "message": "Verify your domain to log in" + }, + "verifyYourDomainDescription": { + "message": "To continue with log in, verify this domain." + }, + "confirmKeyConnectorOrganizationUserDescription": { + "message": "To continue with log in, verify the organization and domain." + }, "confirmNoSelectedCriticalApplicationsTitle": { "message": "No critical applications are selected" }, diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 6164c4e05d3..b26db7e9056 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -184,7 +184,9 @@ import { DefaultChangeKdfApiService } from "@bitwarden/common/key-management/kdf import { ChangeKdfApiService } from "@bitwarden/common/key-management/kdf/change-kdf-api.service.abstraction"; import { DefaultChangeKdfService } from "@bitwarden/common/key-management/kdf/change-kdf.service"; import { ChangeKdfService } from "@bitwarden/common/key-management/kdf/change-kdf.service.abstraction"; +import { KeyConnectorApiService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector-api.service"; import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; +import { DefaultKeyConnectorApiService } from "@bitwarden/common/key-management/key-connector/services/default-key-connector-api.service"; import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/services/key-connector.service"; import { KeyApiService } from "@bitwarden/common/key-management/keys/services/abstractions/key-api-service.abstraction"; import { RotateableKeySetService } from "@bitwarden/common/key-management/keys/services/abstractions/rotateable-key-set.service"; @@ -1835,6 +1837,11 @@ const safeProviders: SafeProvider[] = [ useClass: IpcSessionRepository, deps: [StateProvider], }), + safeProvider({ + provide: KeyConnectorApiService, + useClass: DefaultKeyConnectorApiService, + deps: [ApiServiceAbstraction], + }), safeProvider({ provide: PremiumInterestStateService, useClass: NoopPremiumInterestStateService, diff --git a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts index 8c12060168b..9d7acd7d26e 100644 --- a/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts +++ b/libs/auth/src/angular/two-factor-auth/two-factor-auth.component.spec.ts @@ -421,6 +421,7 @@ describe("TwoFactorAuthComponent", () => { keyConnectorUrl: mockUserDecryptionOpts.noMasterPasswordWithKeyConnector.keyConnectorOption! .keyConnectorUrl, + organizationSsoIdentifier: "test-sso-id", }), ); const authResult = new AuthResult(); diff --git a/libs/common/src/key-management/key-connector/abstractions/key-connector-api.service.ts b/libs/common/src/key-management/key-connector/abstractions/key-connector-api.service.ts new file mode 100644 index 00000000000..10d55bfc3fb --- /dev/null +++ b/libs/common/src/key-management/key-connector/abstractions/key-connector-api.service.ts @@ -0,0 +1,7 @@ +import { KeyConnectorConfirmationDetailsResponse } from "../models/response/key-connector-confirmation-details.response"; + +export abstract class KeyConnectorApiService { + abstract getConfirmationDetails( + orgSsoIdentifier: string, + ): Promise; +} diff --git a/libs/common/src/key-management/key-connector/models/key-connector-domain-confirmation.ts b/libs/common/src/key-management/key-connector/models/key-connector-domain-confirmation.ts index 277057485c1..aa3596c1c99 100644 --- a/libs/common/src/key-management/key-connector/models/key-connector-domain-confirmation.ts +++ b/libs/common/src/key-management/key-connector/models/key-connector-domain-confirmation.ts @@ -1,3 +1,4 @@ export interface KeyConnectorDomainConfirmation { keyConnectorUrl: string; + organizationSsoIdentifier: string; } diff --git a/libs/common/src/key-management/key-connector/models/response/key-connector-confirmation-details.response.ts b/libs/common/src/key-management/key-connector/models/response/key-connector-confirmation-details.response.ts new file mode 100644 index 00000000000..bd6ce14194d --- /dev/null +++ b/libs/common/src/key-management/key-connector/models/response/key-connector-confirmation-details.response.ts @@ -0,0 +1,10 @@ +import { BaseResponse } from "../../../../models/response/base.response"; + +export class KeyConnectorConfirmationDetailsResponse extends BaseResponse { + organizationName: string; + + constructor(response: any) { + super(response); + this.organizationName = this.getResponseProperty("OrganizationName"); + } +} diff --git a/libs/common/src/key-management/key-connector/services/default-key-connector-api.service.spec.ts b/libs/common/src/key-management/key-connector/services/default-key-connector-api.service.spec.ts new file mode 100644 index 00000000000..553ce7a3ba0 --- /dev/null +++ b/libs/common/src/key-management/key-connector/services/default-key-connector-api.service.spec.ts @@ -0,0 +1,54 @@ +import { mock, MockProxy } from "jest-mock-extended"; + +import { ApiService } from "../../../abstractions/api.service"; +import { KeyConnectorConfirmationDetailsResponse } from "../models/response/key-connector-confirmation-details.response"; + +import { DefaultKeyConnectorApiService } from "./default-key-connector-api.service"; + +describe("DefaultKeyConnectorApiService", () => { + let apiService: MockProxy; + let sut: DefaultKeyConnectorApiService; + + beforeEach(() => { + apiService = mock(); + sut = new DefaultKeyConnectorApiService(apiService); + }); + + describe("getConfirmationDetails", () => { + it("encodes orgSsoIdentifier in URL", async () => { + const orgSsoIdentifier = "test org/with special@chars"; + const expectedEncodedIdentifier = encodeURIComponent(orgSsoIdentifier); + const mockResponse = {}; + apiService.send.mockResolvedValue(mockResponse); + + await sut.getConfirmationDetails(orgSsoIdentifier); + + expect(apiService.send).toHaveBeenCalledWith( + "GET", + `/accounts/key-connector/confirmation-details/${expectedEncodedIdentifier}`, + null, + true, + true, + ); + }); + + it("returns expected response", async () => { + const orgSsoIdentifier = "test-org"; + const expectedOrgName = "example"; + const mockResponse = { OrganizationName: expectedOrgName }; + apiService.send.mockResolvedValue(mockResponse); + + const result = await sut.getConfirmationDetails(orgSsoIdentifier); + + expect(result).toBeInstanceOf(KeyConnectorConfirmationDetailsResponse); + expect(result.organizationName).toBe(expectedOrgName); + expect(apiService.send).toHaveBeenCalledWith( + "GET", + "/accounts/key-connector/confirmation-details/test-org", + null, + true, + true, + ); + }); + }); +}); diff --git a/libs/common/src/key-management/key-connector/services/default-key-connector-api.service.ts b/libs/common/src/key-management/key-connector/services/default-key-connector-api.service.ts new file mode 100644 index 00000000000..8bf0cdfed16 --- /dev/null +++ b/libs/common/src/key-management/key-connector/services/default-key-connector-api.service.ts @@ -0,0 +1,20 @@ +import { ApiService } from "../../../abstractions/api.service"; +import { KeyConnectorApiService } from "../abstractions/key-connector-api.service"; +import { KeyConnectorConfirmationDetailsResponse } from "../models/response/key-connector-confirmation-details.response"; + +export class DefaultKeyConnectorApiService implements KeyConnectorApiService { + constructor(private apiService: ApiService) {} + + async getConfirmationDetails( + orgSsoIdentifier: string, + ): Promise { + const r = await this.apiService.send( + "GET", + "/accounts/key-connector/confirmation-details/" + encodeURIComponent(orgSsoIdentifier), + null, + true, + true, + ); + return new KeyConnectorConfirmationDetailsResponse(r); + } +} diff --git a/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts b/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts index bb458ff49f4..45b4f5e4ac6 100644 --- a/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts +++ b/libs/common/src/key-management/key-connector/services/key-connector.service.spec.ts @@ -603,7 +603,10 @@ describe("KeyConnectorService", () => { const data$ = keyConnectorService.requiresDomainConfirmation$(mockUserId); const data = await firstValueFrom(data$); - expect(data).toEqual({ keyConnectorUrl: conversion.keyConnectorUrl }); + expect(data).toEqual({ + keyConnectorUrl: conversion.keyConnectorUrl, + organizationSsoIdentifier: conversion.organizationId, + }); }); it("should return observable of null value when no data is set", async () => { diff --git a/libs/common/src/key-management/key-connector/services/key-connector.service.ts b/libs/common/src/key-management/key-connector/services/key-connector.service.ts index f6730cf8870..8a75034cae1 100644 --- a/libs/common/src/key-management/key-connector/services/key-connector.service.ts +++ b/libs/common/src/key-management/key-connector/services/key-connector.service.ts @@ -202,9 +202,16 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction { } requiresDomainConfirmation$(userId: UserId): Observable { - return this.stateProvider - .getUserState$(NEW_SSO_USER_KEY_CONNECTOR_CONVERSION, userId) - .pipe(map((data) => (data != null ? { keyConnectorUrl: data.keyConnectorUrl } : null))); + return this.stateProvider.getUserState$(NEW_SSO_USER_KEY_CONNECTOR_CONVERSION, userId).pipe( + map((data) => + data != null + ? { + keyConnectorUrl: data.keyConnectorUrl, + organizationSsoIdentifier: data.organizationId, + } + : null, + ), + ); } private handleKeyConnectorError(e: any) { diff --git a/libs/key-management-ui/src/key-connector/confirm-key-connector-domain.component.html b/libs/key-management-ui/src/key-connector/confirm-key-connector-domain.component.html index 11b34a8409f..b3bed15c698 100644 --- a/libs/key-management-ui/src/key-connector/confirm-key-connector-domain.component.html +++ b/libs/key-management-ui/src/key-connector/confirm-key-connector-domain.component.html @@ -8,17 +8,34 @@ {{ "loading" | i18n }} } @else { -
-

{{ "keyConnectorDomain" | i18n }}:

-

{{ keyConnectorUrl }}

-
+ @if (organizationName) { +

{{ "confirmKeyConnectorOrganizationUserDescription" | i18n }}

+ +

{{ "organization" | i18n }}

+

{{ organizationName }}

+ } @else { +

{{ "verifyYourDomainDescription" | i18n }}

+ } + +

+ {{ "domain" | i18n }} + +

+

{{ keyConnectorHostName }}

+
} diff --git a/libs/key-management-ui/src/key-connector/confirm-key-connector-domain.component.spec.ts b/libs/key-management-ui/src/key-connector/confirm-key-connector-domain.component.spec.ts index b53b0a196f5..ad0b783eee3 100644 --- a/libs/key-management-ui/src/key-connector/confirm-key-connector-domain.component.spec.ts +++ b/libs/key-management-ui/src/key-connector/confirm-key-connector-domain.component.spec.ts @@ -2,13 +2,17 @@ import { Router } from "@angular/router"; import { mock } from "jest-mock-extended"; import { of } from "rxjs"; +import { KeyConnectorApiService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector-api.service"; import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; import { KeyConnectorDomainConfirmation } from "@bitwarden/common/key-management/key-connector/models/key-connector-domain-confirmation"; +import { KeyConnectorConfirmationDetailsResponse } from "@bitwarden/common/key-management/key-connector/models/response/key-connector-confirmation-details.response"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; import { SyncService } from "@bitwarden/common/platform/sync"; import { mockAccountServiceWith } from "@bitwarden/common/spec"; import { UserId } from "@bitwarden/common/types/guid"; +import { AnonLayoutWrapperDataService, ToastService } from "@bitwarden/components"; import { ConfirmKeyConnectorDomainComponent } from "./confirm-key-connector-domain.component"; @@ -16,8 +20,10 @@ describe("ConfirmKeyConnectorDomainComponent", () => { let component: ConfirmKeyConnectorDomainComponent; const userId = "test-user-id" as UserId; + const expectedHostName = "key-connector-url.com"; const confirmation: KeyConnectorDomainConfirmation = { keyConnectorUrl: "https://key-connector-url.com", + organizationSsoIdentifier: "org-sso-identifier", }; const mockRouter = mock(); @@ -25,6 +31,10 @@ describe("ConfirmKeyConnectorDomainComponent", () => { const mockKeyConnectorService = mock(); const mockLogService = mock(); const mockMessagingService = mock(); + const mockKeyConnectorApiService = mock(); + const mockToastService = mock(); + const mockI18nService = mock(); + const mockAnonLayoutWrapperDataService = mock(); let mockAccountService = mockAccountServiceWith(userId); const onBeforeNavigation = jest.fn(); @@ -33,6 +43,8 @@ describe("ConfirmKeyConnectorDomainComponent", () => { mockAccountService = mockAccountServiceWith(userId); + mockI18nService.t.mockImplementation((key) => `${key}-used-i18n`); + component = new ConfirmKeyConnectorDomainComponent( mockRouter, mockLogService, @@ -40,6 +52,10 @@ describe("ConfirmKeyConnectorDomainComponent", () => { mockMessagingService, mockSyncService, mockAccountService, + mockKeyConnectorApiService, + mockToastService, + mockI18nService, + mockAnonLayoutWrapperDataService, ); jest.spyOn(component, "onBeforeNavigation").mockImplementation(onBeforeNavigation); @@ -67,17 +83,41 @@ describe("ConfirmKeyConnectorDomainComponent", () => { expect(component.loading).toEqual(true); }); + it("sets organization name to undefined when getOrganizationName throws error", async () => { + mockKeyConnectorApiService.getConfirmationDetails.mockRejectedValue(new Error("API error")); + + await component.ngOnInit(); + + expect(component.organizationName).toBeUndefined(); + expect(component.userId).toEqual(userId); + expect(component.keyConnectorUrl).toEqual(confirmation.keyConnectorUrl); + expect(component.keyConnectorHostName).toEqual(expectedHostName); + expect(component.loading).toEqual(false); + expect(mockAnonLayoutWrapperDataService.setAnonLayoutWrapperData).toHaveBeenCalledWith({ + pageTitle: { key: "verifyYourDomainToLogin" }, + }); + }); + it("should set component properties correctly", async () => { + const expectedOrgName = "Test Organization"; + mockKeyConnectorApiService.getConfirmationDetails.mockResolvedValue({ + organizationName: expectedOrgName, + } as KeyConnectorConfirmationDetailsResponse); + await component.ngOnInit(); expect(component.userId).toEqual(userId); + expect(component.organizationName).toEqual(expectedOrgName); expect(component.keyConnectorUrl).toEqual(confirmation.keyConnectorUrl); + expect(component.keyConnectorHostName).toEqual(expectedHostName); expect(component.loading).toEqual(false); }); }); describe("confirm", () => { - it("should call keyConnectorService.convertNewSsoUserToKeyConnector with full sync and navigation to home page", async () => { + it("calls domain verified toast when organization name is not set", async () => { + mockKeyConnectorApiService.getConfirmationDetails.mockRejectedValue(new Error("API error")); + await component.ngOnInit(); await component.confirm(); @@ -94,6 +134,43 @@ describe("ConfirmKeyConnectorDomainComponent", () => { expect(mockSyncService.fullSync.mock.invocationCallOrder[0]).toBeLessThan( mockMessagingService.send.mock.invocationCallOrder[0], ); + expect(mockToastService.showToast).toHaveBeenCalledWith({ + variant: "success", + message: "domainVerified-used-i18n", + }); + expect(mockMessagingService.send.mock.invocationCallOrder[0]).toBeLessThan( + onBeforeNavigation.mock.invocationCallOrder[0], + ); + expect(onBeforeNavigation.mock.invocationCallOrder[0]).toBeLessThan( + mockRouter.navigate.mock.invocationCallOrder[0], + ); + }); + + it("should call keyConnectorService.convertNewSsoUserToKeyConnector with full sync and navigation to home page", async () => { + mockKeyConnectorApiService.getConfirmationDetails.mockResolvedValue({ + organizationName: "Test Org Name", + } as KeyConnectorConfirmationDetailsResponse); + + await component.ngOnInit(); + + await component.confirm(); + + expect(mockKeyConnectorService.convertNewSsoUserToKeyConnector).toHaveBeenCalledWith(userId); + expect(mockSyncService.fullSync).toHaveBeenCalledWith(true); + expect(mockRouter.navigate).toHaveBeenCalledWith(["/"]); + expect(mockMessagingService.send).toHaveBeenCalledWith("loggedIn"); + expect(onBeforeNavigation).toHaveBeenCalled(); + + expect( + mockKeyConnectorService.convertNewSsoUserToKeyConnector.mock.invocationCallOrder[0], + ).toBeLessThan(mockSyncService.fullSync.mock.invocationCallOrder[0]); + expect(mockSyncService.fullSync.mock.invocationCallOrder[0]).toBeLessThan( + mockMessagingService.send.mock.invocationCallOrder[0], + ); + expect(mockToastService.showToast).toHaveBeenCalledWith({ + variant: "success", + message: "organizationVerified-used-i18n", + }); expect(mockMessagingService.send.mock.invocationCallOrder[0]).toBeLessThan( onBeforeNavigation.mock.invocationCallOrder[0], ); diff --git a/libs/key-management-ui/src/key-connector/confirm-key-connector-domain.component.ts b/libs/key-management-ui/src/key-connector/confirm-key-connector-domain.component.ts index fe96e4620ad..aa65f4c43f9 100644 --- a/libs/key-management-ui/src/key-connector/confirm-key-connector-domain.component.ts +++ b/libs/key-management-ui/src/key-connector/confirm-key-connector-domain.component.ts @@ -5,12 +5,21 @@ import { firstValueFrom } from "rxjs"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { getUserId } from "@bitwarden/common/auth/services/account.service"; +import { KeyConnectorApiService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector-api.service"; import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; import { SyncService } from "@bitwarden/common/platform/sync"; import { UserId } from "@bitwarden/common/types/guid"; -import { BitActionDirective, ButtonModule } from "@bitwarden/components"; +import { + AnonLayoutWrapperDataService, + BitActionDirective, + ButtonModule, + IconButtonModule, + ToastService, +} from "@bitwarden/components"; import { I18nPipe } from "@bitwarden/ui-common"; // FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush @@ -19,11 +28,13 @@ import { I18nPipe } from "@bitwarden/ui-common"; selector: "confirm-key-connector-domain", templateUrl: "confirm-key-connector-domain.component.html", standalone: true, - imports: [CommonModule, ButtonModule, I18nPipe, BitActionDirective], + imports: [CommonModule, ButtonModule, I18nPipe, BitActionDirective, IconButtonModule], }) export class ConfirmKeyConnectorDomainComponent implements OnInit { loading = true; keyConnectorUrl!: string; + keyConnectorHostName!: string; + organizationName: string | undefined; userId!: UserId; // FIXME(https://bitwarden.atlassian.net/browse/CL-903): Migrate to Signals @@ -37,6 +48,10 @@ export class ConfirmKeyConnectorDomainComponent implements OnInit { private messagingService: MessagingService, private syncService: SyncService, private accountService: AccountService, + private keyConnectorApiService: KeyConnectorApiService, + private toastService: ToastService, + private i18nService: I18nService, + private anonLayoutWrapperDataService: AnonLayoutWrapperDataService, ) {} async ngOnInit() { @@ -57,14 +72,36 @@ export class ConfirmKeyConnectorDomainComponent implements OnInit { return; } - this.keyConnectorUrl = confirmation.keyConnectorUrl; + this.organizationName = await this.getOrganizationName(confirmation.organizationSsoIdentifier); + // PM-29133 Remove during cleanup. + if (this.organizationName == undefined) { + this.anonLayoutWrapperDataService.setAnonLayoutWrapperData({ + pageTitle: { key: "verifyYourDomainToLogin" }, + }); + } + + this.keyConnectorUrl = confirmation.keyConnectorUrl; + this.keyConnectorHostName = Utils.getHostname(confirmation.keyConnectorUrl); this.loading = false; } confirm = async () => { await this.keyConnectorService.convertNewSsoUserToKeyConnector(this.userId); + if (this.organizationName) { + this.toastService.showToast({ + variant: "success", + message: this.i18nService.t("organizationVerified"), + }); + } else { + // PM-29133 Remove during cleanup. + this.toastService.showToast({ + variant: "success", + message: this.i18nService.t("domainVerified"), + }); + } + await this.syncService.fullSync(true); this.messagingService.send("loggedIn"); @@ -77,4 +114,22 @@ export class ConfirmKeyConnectorDomainComponent implements OnInit { cancel = async () => { this.messagingService.send("logout"); }; + + private async getOrganizationName( + organizationSsoIdentifier: string, + ): Promise { + try { + const details = + await this.keyConnectorApiService.getConfirmationDetails(organizationSsoIdentifier); + return details.organizationName; + } catch (error) { + // PM-29133 Remove during cleanup. + // Old self hosted servers may not have this endpoint yet. On error log a warning and continue without organization name. + this.logService.warning( + `[ConfirmKeyConnectorDomainComponent] Unable to get key connector confirmation details for organizationSsoIdentifier ${organizationSsoIdentifier}:`, + error, + ); + return undefined; + } + } } diff --git a/libs/key-management-ui/src/key-connector/remove-password.component.html b/libs/key-management-ui/src/key-connector/remove-password.component.html new file mode 100644 index 00000000000..42cee2fe168 --- /dev/null +++ b/libs/key-management-ui/src/key-connector/remove-password.component.html @@ -0,0 +1,35 @@ +@if (loading) { +
+ + {{ "loading" | i18n }} +
+} @else { +

{{ "removeMasterPasswordForOrgUserKeyConnector" | i18n }}

+

{{ "organization" | i18n }}

+

{{ organization.name }}

+

+ {{ "domain" | i18n }} + +

+

{{ keyConnectorHostName }}

+ +
+ + + +
+} diff --git a/libs/key-management-ui/src/key-connector/remove-password.component.spec.ts b/libs/key-management-ui/src/key-connector/remove-password.component.spec.ts index eb11932d931..240cddee2f9 100644 --- a/libs/key-management-ui/src/key-connector/remove-password.component.spec.ts +++ b/libs/key-management-ui/src/key-connector/remove-password.component.spec.ts @@ -19,6 +19,7 @@ describe("RemovePasswordComponent", () => { let component: RemovePasswordComponent; const userId = "test-user-id" as UserId; + const expectedHostName = "key-connector-url.com"; const organization = { id: "test-organization-id", name: "test-organization-name", @@ -62,6 +63,7 @@ describe("RemovePasswordComponent", () => { expect(component["activeUserId"]).toBe("test-user-id"); expect(component.organization).toEqual(organization); expect(component.loading).toEqual(false); + expect(component.keyConnectorHostName).toBe(expectedHostName); expect(mockKeyConnectorService.getManagingOrganization).toHaveBeenCalledWith(userId); expect(mockSyncService.fullSync).toHaveBeenCalledWith(false); diff --git a/libs/key-management-ui/src/key-connector/remove-password.component.ts b/libs/key-management-ui/src/key-connector/remove-password.component.ts index a31989ffc49..bb17475230d 100644 --- a/libs/key-management-ui/src/key-connector/remove-password.component.ts +++ b/libs/key-management-ui/src/key-connector/remove-password.component.ts @@ -1,4 +1,5 @@ -import { Directive, OnInit } from "@angular/core"; +import { CommonModule } from "@angular/common"; +import { Component, OnInit } from "@angular/core"; import { Router } from "@angular/router"; import { firstValueFrom } from "rxjs"; @@ -9,17 +10,33 @@ import { KeyConnectorService } from "@bitwarden/common/key-management/key-connec import { ErrorResponse } from "@bitwarden/common/models/response/error.response"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; +import { Utils } from "@bitwarden/common/platform/misc/utils"; import { UserId } from "@bitwarden/common/types/guid"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { DialogService, ToastService } from "@bitwarden/components"; +import { + DialogService, + ToastService, + ButtonModule, + BitActionDirective, + IconButtonModule, +} from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; -@Directive() +// FIXME(https://bitwarden.atlassian.net/browse/CL-764): Migrate to OnPush +// eslint-disable-next-line @angular-eslint/prefer-on-push-component-change-detection +@Component({ + selector: "km-ui-remove-password", + templateUrl: "remove-password.component.html", + standalone: true, + imports: [CommonModule, ButtonModule, I18nPipe, BitActionDirective, IconButtonModule], +}) export class RemovePasswordComponent implements OnInit { continuing = false; leaving = false; loading = true; organization!: Organization; + keyConnectorHostName!: string; private activeUserId!: UserId; constructor( @@ -55,6 +72,7 @@ export class RemovePasswordComponent implements OnInit { await this.router.navigate(["/"]); return; } + this.keyConnectorHostName = Utils.getHostname(this.organization.keyConnectorUrl); this.loading = false; } @@ -73,7 +91,7 @@ export class RemovePasswordComponent implements OnInit { this.toastService.showToast({ variant: "success", - message: this.i18nService.t("removedMasterPassword"), + message: this.i18nService.t("organizationVerified"), }); await this.router.navigate(["/"]); @@ -86,9 +104,11 @@ export class RemovePasswordComponent implements OnInit { leave = async () => { const confirmed = await this.dialogService.openSimpleDialog({ - title: this.organization.name, - content: { key: "leaveOrganizationConfirmation" }, + title: { key: "leaveOrganization" }, + content: { key: "leaveOrganizationContent" }, type: "warning", + acceptButtonText: { key: "leaveNow" }, + cancelButtonText: { key: "cancel" }, }); if (!confirmed) {