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) {