-
-
-
-
+
+
+
+ {{ "loading" | i18n }}
-
-
+
+
+
+
+
+
diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.ts
index 079e68fe21..83038d1bfc 100644
--- a/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.ts
+++ b/bitwarden_license/bit-web/src/app/admin-console/providers/settings/account.component.ts
@@ -1,13 +1,18 @@
import { Component } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
+import { UserVerificationDialogComponent } from "@bitwarden/auth/angular";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
+import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction";
import { ProviderUpdateRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-update.request";
import { ProviderResponse } from "@bitwarden/common/admin-console/models/response/provider/provider.response";
+import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
+import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
+import { DialogService } from "@bitwarden/components";
@Component({
selector: "provider-account",
@@ -23,6 +28,11 @@ export class AccountComponent {
private providerId: string;
+ protected enableDeleteProvider$ = this.configService.getFeatureFlag$(
+ FeatureFlag.EnableDeleteProvider,
+ false,
+ );
+
constructor(
private apiService: ApiService,
private i18nService: I18nService,
@@ -30,6 +40,9 @@ export class AccountComponent {
private syncService: SyncService,
private platformUtilsService: PlatformUtilsService,
private logService: LogService,
+ private dialogService: DialogService,
+ private configService: ConfigService,
+ private providerApiService: ProviderApiServiceAbstraction,
) {}
async ngOnInit() {
@@ -38,7 +51,7 @@ export class AccountComponent {
this.route.parent.parent.params.subscribe(async (params) => {
this.providerId = params.providerId;
try {
- this.provider = await this.apiService.getProvider(this.providerId);
+ this.provider = await this.providerApiService.getProvider(this.providerId);
} catch (e) {
this.logService.error(`Handled exception: ${e}`);
}
@@ -53,7 +66,7 @@ export class AccountComponent {
request.businessName = this.provider.businessName;
request.billingEmail = this.provider.billingEmail;
- this.formPromise = this.apiService.putProvider(this.providerId, request).then(() => {
+ this.formPromise = this.providerApiService.putProvider(this.providerId, request).then(() => {
return this.syncService.fullSync(true);
});
await this.formPromise;
@@ -62,4 +75,60 @@ export class AccountComponent {
this.logService.error(`Handled exception: ${e}`);
}
}
+
+ async deleteProvider() {
+ const providerClients = await this.apiService.getProviderClients(this.providerId);
+ if (providerClients.data != null && providerClients.data.length > 0) {
+ await this.dialogService.openSimpleDialog({
+ title: { key: "deleteProviderName", placeholders: [this.provider.name] },
+ content: { key: "deleteProviderWarningDesc", placeholders: [this.provider.name] },
+ acceptButtonText: { key: "ok" },
+ type: "danger",
+ });
+
+ return false;
+ }
+
+ const userVerified = await this.verifyUser();
+ if (!userVerified) {
+ return;
+ }
+
+ this.formPromise = this.providerApiService.deleteProvider(this.providerId);
+ try {
+ await this.formPromise;
+ this.platformUtilsService.showToast(
+ "success",
+ this.i18nService.t("providerDeleted"),
+ this.i18nService.t("providerDeletedDesc"),
+ );
+ } catch (e) {
+ this.logService.error(e);
+ }
+ this.formPromise = null;
+ }
+
+ private async verifyUser(): Promise
{
+ const confirmDescription = "deleteProviderConfirmation";
+ const result = await UserVerificationDialogComponent.open(this.dialogService, {
+ title: "deleteProvider",
+ bodyText: confirmDescription,
+ confirmButtonOptions: {
+ text: "deleteProvider",
+ type: "danger",
+ },
+ });
+
+ // Handle the result of the dialog based on user action and verification success
+ if (result.userAction === "cancel") {
+ // User cancelled the dialog
+ return false;
+ }
+
+ // User confirmed the dialog so check verification success
+ if (!result.verificationSuccess) {
+ return false;
+ }
+ return true;
+ }
}
diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts
index ed7b42c959..cf9af4f68a 100644
--- a/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts
+++ b/bitwarden_license/bit-web/src/app/admin-console/providers/setup/setup.component.ts
@@ -3,7 +3,7 @@ import { ActivatedRoute, Router } from "@angular/router";
import { firstValueFrom } from "rxjs";
import { first } from "rxjs/operators";
-import { ApiService } from "@bitwarden/common/abstractions/api.service";
+import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction";
import { ProviderSetupRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-setup.request";
import { ExpandedTaxInfoUpdateRequest } from "@bitwarden/common/billing/models/request/expanded-tax-info-update.request";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
@@ -50,10 +50,10 @@ export class SetupComponent implements OnInit {
private i18nService: I18nService,
private route: ActivatedRoute,
private cryptoService: CryptoService,
- private apiService: ApiService,
private syncService: SyncService,
private validationService: ValidationService,
private configService: ConfigService,
+ private providerApiService: ProviderApiServiceAbstraction,
) {}
ngOnInit() {
@@ -80,7 +80,7 @@ export class SetupComponent implements OnInit {
// Check if provider exists, redirect if it does
try {
- const provider = await this.apiService.getProvider(this.providerId);
+ const provider = await this.providerApiService.getProvider(this.providerId);
if (provider.name != null) {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
@@ -128,7 +128,7 @@ export class SetupComponent implements OnInit {
}
}
- const provider = await this.apiService.postProviderSetup(this.providerId, request);
+ const provider = await this.providerApiService.postProviderSetup(this.providerId, request);
this.platformUtilsService.showToast("success", null, this.i18nService.t("providerSetup"));
await this.syncService.fullSync(true);
diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts
index dbb94f6753..ad0881a4b3 100644
--- a/libs/angular/src/services/jslib-services.module.ts
+++ b/libs/angular/src/services/jslib-services.module.ts
@@ -38,6 +38,7 @@ import {
InternalPolicyService,
PolicyService as PolicyServiceAbstraction,
} from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
+import { ProviderApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider/provider-api.service.abstraction";
import { ProviderService as ProviderServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider.service";
import { OrganizationApiService } from "@bitwarden/common/admin-console/services/organization/organization-api.service";
import { OrganizationService } from "@bitwarden/common/admin-console/services/organization/organization.service";
@@ -47,6 +48,7 @@ import { DefaultOrganizationManagementPreferencesService } from "@bitwarden/comm
import { OrganizationUserServiceImplementation } from "@bitwarden/common/admin-console/services/organization-user/organization-user.service.implementation";
import { PolicyApiService } from "@bitwarden/common/admin-console/services/policy/policy-api.service";
import { PolicyService } from "@bitwarden/common/admin-console/services/policy/policy.service";
+import { ProviderApiService } from "@bitwarden/common/admin-console/services/provider/provider-api.service";
import { ProviderService } from "@bitwarden/common/admin-console/services/provider.service";
import { AccountApiService as AccountApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/account-api.service";
import {
@@ -1115,6 +1117,11 @@ const safeProviders: SafeProvider[] = [
useClass: LoggingErrorHandler,
deps: [],
}),
+ safeProvider({
+ provide: ProviderApiServiceAbstraction,
+ useClass: ProviderApiService,
+ deps: [ApiServiceAbstraction],
+ }),
];
function encryptServiceFactory(
diff --git a/libs/common/src/abstractions/api.service.ts b/libs/common/src/abstractions/api.service.ts
index 44a58403ed..9b3160ee19 100644
--- a/libs/common/src/abstractions/api.service.ts
+++ b/libs/common/src/abstractions/api.service.ts
@@ -4,8 +4,6 @@ import { OrganizationSponsorshipRedeemRequest } from "../admin-console/models/re
import { OrganizationConnectionRequest } from "../admin-console/models/request/organization-connection.request";
import { ProviderAddOrganizationRequest } from "../admin-console/models/request/provider/provider-add-organization.request";
import { ProviderOrganizationCreateRequest } from "../admin-console/models/request/provider/provider-organization-create.request";
-import { ProviderSetupRequest } from "../admin-console/models/request/provider/provider-setup.request";
-import { ProviderUpdateRequest } from "../admin-console/models/request/provider/provider-update.request";
import { ProviderUserAcceptRequest } from "../admin-console/models/request/provider/provider-user-accept.request";
import { ProviderUserBulkConfirmRequest } from "../admin-console/models/request/provider/provider-user-bulk-confirm.request";
import { ProviderUserBulkRequest } from "../admin-console/models/request/provider/provider-user-bulk.request";
@@ -29,7 +27,6 @@ import {
ProviderUserResponse,
ProviderUserUserDetailsResponse,
} from "../admin-console/models/response/provider/provider-user.response";
-import { ProviderResponse } from "../admin-console/models/response/provider/provider.response";
import { SelectionReadOnlyResponse } from "../admin-console/models/response/selection-read-only.response";
import { CreateAuthRequest } from "../auth/models/request/create-auth.request";
import { DeviceVerificationRequest } from "../auth/models/request/device-verification.request";
@@ -372,10 +369,6 @@ export abstract class ApiService {
getPlans: () => Promise>;
getTaxRates: () => Promise>;
- postProviderSetup: (id: string, request: ProviderSetupRequest) => Promise;
- getProvider: (id: string) => Promise;
- putProvider: (id: string, request: ProviderUpdateRequest) => Promise;
-
getProviderUsers: (providerId: string) => Promise>;
getProviderUser: (providerId: string, id: string) => Promise;
postProviderUserInvite: (providerId: string, request: ProviderUserInviteRequest) => Promise;
diff --git a/libs/common/src/admin-console/abstractions/provider/provider-api.service.abstraction.ts b/libs/common/src/admin-console/abstractions/provider/provider-api.service.abstraction.ts
new file mode 100644
index 0000000000..3c2170bf9e
--- /dev/null
+++ b/libs/common/src/admin-console/abstractions/provider/provider-api.service.abstraction.ts
@@ -0,0 +1,15 @@
+import { ProviderSetupRequest } from "../../models/request/provider/provider-setup.request";
+import { ProviderUpdateRequest } from "../../models/request/provider/provider-update.request";
+import { ProviderVerifyRecoverDeleteRequest } from "../../models/request/provider/provider-verify-recover-delete.request";
+import { ProviderResponse } from "../../models/response/provider/provider.response";
+
+export class ProviderApiServiceAbstraction {
+ postProviderSetup: (id: string, request: ProviderSetupRequest) => Promise;
+ getProvider: (id: string) => Promise;
+ putProvider: (id: string, request: ProviderUpdateRequest) => Promise;
+ providerRecoverDeleteToken: (
+ organizationId: string,
+ request: ProviderVerifyRecoverDeleteRequest,
+ ) => Promise;
+ deleteProvider: (id: string) => Promise;
+}
diff --git a/libs/common/src/admin-console/models/request/provider/provider-verify-recover-delete.request.ts b/libs/common/src/admin-console/models/request/provider/provider-verify-recover-delete.request.ts
new file mode 100644
index 0000000000..528d2dba78
--- /dev/null
+++ b/libs/common/src/admin-console/models/request/provider/provider-verify-recover-delete.request.ts
@@ -0,0 +1,7 @@
+export class ProviderVerifyRecoverDeleteRequest {
+ token: string;
+
+ constructor(token: string) {
+ this.token = token;
+ }
+}
diff --git a/libs/common/src/admin-console/services/provider/provider-api.service.ts b/libs/common/src/admin-console/services/provider/provider-api.service.ts
new file mode 100644
index 0000000000..2ee921393f
--- /dev/null
+++ b/libs/common/src/admin-console/services/provider/provider-api.service.ts
@@ -0,0 +1,47 @@
+import { ApiService } from "../../../abstractions/api.service";
+import { ProviderApiServiceAbstraction } from "../../abstractions/provider/provider-api.service.abstraction";
+import { ProviderSetupRequest } from "../../models/request/provider/provider-setup.request";
+import { ProviderUpdateRequest } from "../../models/request/provider/provider-update.request";
+import { ProviderVerifyRecoverDeleteRequest } from "../../models/request/provider/provider-verify-recover-delete.request";
+import { ProviderResponse } from "../../models/response/provider/provider.response";
+
+export class ProviderApiService implements ProviderApiServiceAbstraction {
+ constructor(private apiService: ApiService) {}
+ async postProviderSetup(id: string, request: ProviderSetupRequest) {
+ const r = await this.apiService.send(
+ "POST",
+ "/providers/" + id + "/setup",
+ request,
+ true,
+ true,
+ );
+ return new ProviderResponse(r);
+ }
+
+ async getProvider(id: string) {
+ const r = await this.apiService.send("GET", "/providers/" + id, null, true, true);
+ return new ProviderResponse(r);
+ }
+
+ async putProvider(id: string, request: ProviderUpdateRequest) {
+ const r = await this.apiService.send("PUT", "/providers/" + id, request, true, true);
+ return new ProviderResponse(r);
+ }
+
+ providerRecoverDeleteToken(
+ providerId: string,
+ request: ProviderVerifyRecoverDeleteRequest,
+ ): Promise {
+ return this.apiService.send(
+ "POST",
+ "/providers/" + providerId + "/delete-recover-token",
+ request,
+ false,
+ false,
+ );
+ }
+
+ async deleteProvider(id: string): Promise {
+ await this.apiService.send("DELETE", "/providers/" + id, null, true, false);
+ }
+}
diff --git a/libs/common/src/enums/feature-flag.enum.ts b/libs/common/src/enums/feature-flag.enum.ts
index b937e6c462..636e9bc4ce 100644
--- a/libs/common/src/enums/feature-flag.enum.ts
+++ b/libs/common/src/enums/feature-flag.enum.ts
@@ -10,6 +10,7 @@ export enum FeatureFlag {
EnableConsolidatedBilling = "enable-consolidated-billing",
AC1795_UpdatedSubscriptionStatusSection = "AC-1795_updated-subscription-status-section",
UnassignedItemsBanner = "unassigned-items-banner",
+ EnableDeleteProvider = "AC-1218-delete-provider",
}
// Replace this with a type safe lookup of the feature flag values in PM-2282
diff --git a/libs/common/src/services/api.service.ts b/libs/common/src/services/api.service.ts
index 90671c4f04..e8135f3d6c 100644
--- a/libs/common/src/services/api.service.ts
+++ b/libs/common/src/services/api.service.ts
@@ -7,8 +7,6 @@ import { OrganizationSponsorshipRedeemRequest } from "../admin-console/models/re
import { OrganizationConnectionRequest } from "../admin-console/models/request/organization-connection.request";
import { ProviderAddOrganizationRequest } from "../admin-console/models/request/provider/provider-add-organization.request";
import { ProviderOrganizationCreateRequest } from "../admin-console/models/request/provider/provider-organization-create.request";
-import { ProviderSetupRequest } from "../admin-console/models/request/provider/provider-setup.request";
-import { ProviderUpdateRequest } from "../admin-console/models/request/provider/provider-update.request";
import { ProviderUserAcceptRequest } from "../admin-console/models/request/provider/provider-user-accept.request";
import { ProviderUserBulkConfirmRequest } from "../admin-console/models/request/provider/provider-user-bulk-confirm.request";
import { ProviderUserBulkRequest } from "../admin-console/models/request/provider/provider-user-bulk.request";
@@ -32,7 +30,6 @@ import {
ProviderUserResponse,
ProviderUserUserDetailsResponse,
} from "../admin-console/models/response/provider/provider-user.response";
-import { ProviderResponse } from "../admin-console/models/response/provider/provider.response";
import { SelectionReadOnlyResponse } from "../admin-console/models/response/selection-read-only.response";
import { TokenService } from "../auth/abstractions/token.service";
import { CreateAuthRequest } from "../auth/models/request/create-auth.request";
@@ -1151,23 +1148,6 @@ export class ApiService implements ApiServiceAbstraction {
return this.send("DELETE", "/organizations/connections/" + id, null, true, false);
}
- // Provider APIs
-
- async postProviderSetup(id: string, request: ProviderSetupRequest) {
- const r = await this.send("POST", "/providers/" + id + "/setup", request, true, true);
- return new ProviderResponse(r);
- }
-
- async getProvider(id: string) {
- const r = await this.send("GET", "/providers/" + id, null, true, true);
- return new ProviderResponse(r);
- }
-
- async putProvider(id: string, request: ProviderUpdateRequest) {
- const r = await this.send("PUT", "/providers/" + id, request, true, true);
- return new ProviderResponse(r);
- }
-
// Provider User APIs
async getProviderUsers(