diff --git a/apps/web/src/app/admin-console/organizations/manage/verify-recover-delete-org.component.html b/apps/web/src/app/admin-console/organizations/manage/verify-recover-delete-org.component.html new file mode 100644 index 0000000000..a689e98985 --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/manage/verify-recover-delete-org.component.html @@ -0,0 +1,39 @@ +
+
+

{{ "deleteOrganization" | i18n }}

+
+
+ {{ + "deletingOrganizationIsPermanentWarning" | i18n: name + }} +

+ {{ name }} +

+

{{ "deleteRecoverOrgConfirmDesc" | i18n }}

+

{{ "deletingOrganizationActiveUserAccountsWarning" | i18n }}

+
+
+ + + {{ "cancel" | i18n }} + +
+
+
+
+
diff --git a/apps/web/src/app/admin-console/organizations/manage/verify-recover-delete-org.component.ts b/apps/web/src/app/admin-console/organizations/manage/verify-recover-delete-org.component.ts new file mode 100644 index 0000000000..0039347dc6 --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/manage/verify-recover-delete-org.component.ts @@ -0,0 +1,54 @@ +import { Component, OnInit } from "@angular/core"; +import { ActivatedRoute, Router } from "@angular/router"; +import { firstValueFrom } from "rxjs"; + +import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction"; +import { OrganizationVerifyDeleteRecoverRequest } from "@bitwarden/common/admin-console/models/request/organization-verify-delete-recover.request"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; + +import { SharedModule } from "../../../shared/shared.module"; + +@Component({ + templateUrl: "verify-recover-delete-org.component.html", + standalone: true, + imports: [SharedModule], +}) +export class VerifyRecoverDeleteOrgComponent implements OnInit { + loading = true; + name: string; + + private orgId: string; + private token: string; + + constructor( + private router: Router, + private apiService: OrganizationApiServiceAbstraction, + private platformUtilsService: PlatformUtilsService, + private i18nService: I18nService, + private route: ActivatedRoute, + ) {} + + async ngOnInit() { + const qParams = await firstValueFrom(this.route.queryParams); + if (qParams.orgId != null && qParams.token != null && qParams.name != null) { + this.orgId = qParams.orgId; + this.token = qParams.token; + this.name = qParams.name; + this.loading = false; + } else { + await this.router.navigate(["/"]); + } + } + + submit = async () => { + const request = new OrganizationVerifyDeleteRecoverRequest(this.token); + await this.apiService.deleteUsingToken(this.orgId, request); + this.platformUtilsService.showToast( + "success", + this.i18nService.t("organizationDeleted"), + this.i18nService.t("organizationDeletedDesc"), + ); + await this.router.navigate(["/"]); + }; +} diff --git a/apps/web/src/app/oss-routing.module.ts b/apps/web/src/app/oss-routing.module.ts index 066ed5db10..93d68d31b3 100644 --- a/apps/web/src/app/oss-routing.module.ts +++ b/apps/web/src/app/oss-routing.module.ts @@ -11,6 +11,7 @@ import { import { flagEnabled, Flags } from "../utils/flags"; +import { VerifyRecoverDeleteOrgComponent } from "./admin-console/organizations/manage/verify-recover-delete-org.component"; import { AcceptFamilySponsorshipComponent } from "./admin-console/organizations/sponsorships/accept-family-sponsorship.component"; import { FamiliesForEnterpriseSetupComponent } from "./admin-console/organizations/sponsorships/families-for-enterprise-setup.component"; import { VerifyRecoverDeleteProviderComponent } from "./admin-console/providers/verify-recover-delete-provider.component"; @@ -157,6 +158,12 @@ const routes: Routes = [ canActivate: [UnauthGuard], data: { titleId: "deleteAccount" }, }, + { + path: "verify-recover-delete-org", + component: VerifyRecoverDeleteOrgComponent, + canActivate: [UnauthGuard], + data: { titleId: "deleteOrganization" }, + }, { path: "verify-recover-delete-provider", component: VerifyRecoverDeleteProviderComponent, diff --git a/apps/web/src/app/shared/loose-components.module.ts b/apps/web/src/app/shared/loose-components.module.ts index c7ae63f25c..ce5f442bd2 100644 --- a/apps/web/src/app/shared/loose-components.module.ts +++ b/apps/web/src/app/shared/loose-components.module.ts @@ -9,6 +9,7 @@ import { LayoutComponent, NavigationModule } from "@bitwarden/components"; import { OrganizationLayoutComponent } from "../admin-console/organizations/layouts/organization-layout.component"; import { EventsComponent as OrgEventsComponent } from "../admin-console/organizations/manage/events.component"; import { UserConfirmComponent as OrgUserConfirmComponent } from "../admin-console/organizations/manage/user-confirm.component"; +import { VerifyRecoverDeleteOrgComponent } from "../admin-console/organizations/manage/verify-recover-delete-org.component"; import { AcceptFamilySponsorshipComponent } from "../admin-console/organizations/sponsorships/accept-family-sponsorship.component"; import { ExposedPasswordsReportComponent as OrgExposedPasswordsReportComponent } from "../admin-console/organizations/tools/exposed-passwords-report.component"; import { InactiveTwoFactorReportComponent as OrgInactiveTwoFactorReportComponent } from "../admin-console/organizations/tools/inactive-two-factor-report.component"; @@ -115,6 +116,7 @@ import { SharedModule } from "./shared.module"; OrganizationLayoutComponent, UserLayoutComponent, PaymentMethodWarningsModule, + VerifyRecoverDeleteOrgComponent, ], declarations: [ AcceptFamilySponsorshipComponent, diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index e79497d25e..f2546e38ec 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -1341,6 +1341,9 @@ "accountDeletedDesc": { "message": "Your account has been closed and all associated data has been deleted." }, + "deleteOrganizationWarning": { + "message": "Deleting your organization is permanent. It cannot be undone." + }, "myAccount": { "message": "My account" }, @@ -3388,6 +3391,9 @@ "deleteRecoverConfirmDesc": { "message": "You have requested to delete your Bitwarden account. Use the button below to confirm." }, + "deleteRecoverOrgConfirmDesc": { + "message": "You have requested to delete your Bitwarden organization." + }, "myOrganization": { "message": "My organization" }, diff --git a/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts b/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts index 66a05cf613..3aca2fb3f6 100644 --- a/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts +++ b/libs/common/src/admin-console/abstractions/organization/organization-api.service.abstraction.ts @@ -23,6 +23,7 @@ import { OrganizationCreateRequest } from "../../models/request/organization-cre import { OrganizationKeysRequest } from "../../models/request/organization-keys.request"; import { OrganizationUpdateRequest } from "../../models/request/organization-update.request"; import { OrganizationUpgradeRequest } from "../../models/request/organization-upgrade.request"; +import { OrganizationVerifyDeleteRecoverRequest } from "../../models/request/organization-verify-delete-recover.request"; import { OrganizationApiKeyInformationResponse } from "../../models/response/organization-api-key-information.response"; import { OrganizationAutoEnrollStatusResponse } from "../../models/response/organization-auto-enroll-status.response"; import { OrganizationKeysResponse } from "../../models/response/organization-keys.response"; @@ -54,6 +55,10 @@ export class OrganizationApiServiceAbstraction { reinstate: (id: string) => Promise; leave: (id: string) => Promise; delete: (id: string, request: SecretVerificationRequest) => Promise; + deleteUsingToken: ( + organizationId: string, + request: OrganizationVerifyDeleteRecoverRequest, + ) => Promise; updateLicense: (id: string, data: FormData) => Promise; importDirectory: (organizationId: string, request: ImportDirectoryRequest) => Promise; getOrCreateApiKey: (id: string, request: OrganizationApiKeyRequest) => Promise; diff --git a/libs/common/src/admin-console/models/request/organization-verify-delete-recover.request.ts b/libs/common/src/admin-console/models/request/organization-verify-delete-recover.request.ts new file mode 100644 index 0000000000..c6e08ace40 --- /dev/null +++ b/libs/common/src/admin-console/models/request/organization-verify-delete-recover.request.ts @@ -0,0 +1,7 @@ +export class OrganizationVerifyDeleteRecoverRequest { + token: string; + + constructor(token: string) { + this.token = token; + } +} diff --git a/libs/common/src/admin-console/services/organization/organization-api.service.ts b/libs/common/src/admin-console/services/organization/organization-api.service.ts index 262232a964..91d5e47547 100644 --- a/libs/common/src/admin-console/services/organization/organization-api.service.ts +++ b/libs/common/src/admin-console/services/organization/organization-api.service.ts @@ -26,6 +26,7 @@ import { OrganizationCreateRequest } from "../../models/request/organization-cre import { OrganizationKeysRequest } from "../../models/request/organization-keys.request"; import { OrganizationUpdateRequest } from "../../models/request/organization-update.request"; import { OrganizationUpgradeRequest } from "../../models/request/organization-upgrade.request"; +import { OrganizationVerifyDeleteRecoverRequest } from "../../models/request/organization-verify-delete-recover.request"; import { OrganizationApiKeyInformationResponse } from "../../models/response/organization-api-key-information.response"; import { OrganizationAutoEnrollStatusResponse } from "../../models/response/organization-auto-enroll-status.response"; import { OrganizationKeysResponse } from "../../models/response/organization-keys.response"; @@ -198,6 +199,19 @@ export class OrganizationApiService implements OrganizationApiServiceAbstraction await this.syncService.fullSync(true); } + deleteUsingToken( + organizationId: string, + request: OrganizationVerifyDeleteRecoverRequest, + ): Promise { + return this.apiService.send( + "POST", + "/organizations/" + organizationId + "/delete-recover-token", + request, + false, + false, + ); + } + async updateLicense(id: string, data: FormData): Promise { await this.apiService.send( "POST",