diff --git a/apps/web/src/app/organizations/billing/organization-billing.module.ts b/apps/web/src/app/organizations/billing/organization-billing.module.ts index 71222e3ba5f..17d825fbae7 100644 --- a/apps/web/src/app/organizations/billing/organization-billing.module.ts +++ b/apps/web/src/app/organizations/billing/organization-billing.module.ts @@ -11,6 +11,7 @@ import { OrganizationBillingRoutingModule } from "./organization-billing-routing import { OrganizationBillingTabComponent } from "./organization-billing-tab.component"; import { OrganizationSubscriptionCloudComponent } from "./organization-subscription-cloud.component"; import { OrganizationSubscriptionSelfhostComponent } from "./organization-subscription-selfhost.component"; +import { SecretsManagerEnrollComponent } from "./secrets-manager/enroll.component"; import { SubscriptionHiddenComponent } from "./subscription-hidden.component"; @NgModule({ @@ -25,6 +26,7 @@ import { SubscriptionHiddenComponent } from "./subscription-hidden.component"; OrganizationSubscriptionSelfhostComponent, OrganizationSubscriptionCloudComponent, SubscriptionHiddenComponent, + SecretsManagerEnrollComponent, ], }) export class OrganizationBillingModule {} diff --git a/apps/web/src/app/organizations/billing/organization-subscription-cloud.component.html b/apps/web/src/app/organizations/billing/organization-subscription-cloud.component.html index 25dc12dc2fb..3e7fda5cafc 100644 --- a/apps/web/src/app/organizations/billing/organization-subscription-cloud.component.html +++ b/apps/web/src/app/organizations/billing/organization-subscription-cloud.component.html @@ -108,6 +108,13 @@ *ngIf="showChangePlan" > + + +

{{ "manageSubscription" | i18n }}

{{ subscriptionDesc }}

i.sponsoredSubscriptionItem); } diff --git a/apps/web/src/app/organizations/billing/secrets-manager/enroll.component.html b/apps/web/src/app/organizations/billing/secrets-manager/enroll.component.html new file mode 100644 index 00000000000..56a48e09fe7 --- /dev/null +++ b/apps/web/src/app/organizations/billing/secrets-manager/enroll.component.html @@ -0,0 +1,13 @@ +
+

{{ "secretsManagerBeta" | i18n }}

+

{{ "secretsManagerSubscriptionDesc" | i18n }}

+ + + + {{ "secretsManagerEnable" | i18n }} + + + +
diff --git a/apps/web/src/app/organizations/billing/secrets-manager/enroll.component.ts b/apps/web/src/app/organizations/billing/secrets-manager/enroll.component.ts new file mode 100644 index 00000000000..e28e089a3a9 --- /dev/null +++ b/apps/web/src/app/organizations/billing/secrets-manager/enroll.component.ts @@ -0,0 +1,52 @@ +import { Component, Input, OnInit } from "@angular/core"; +import { FormBuilder } from "@angular/forms"; + +import { I18nService } from "@bitwarden/common/abstractions/i18n.service"; +import { OrganizationApiServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization-api.service.abstraction"; +import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service"; +import { OrganizationEnrollSecretsManagerRequest } from "@bitwarden/common/models/request/organization/organization-enroll-secrets-manager.request"; +import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; + +import { flagEnabled } from "../../../../utils/flags"; + +@Component({ + selector: "sm-enroll", + templateUrl: "enroll.component.html", +}) +export class SecretsManagerEnrollComponent implements OnInit { + @Input() enabled: boolean; + @Input() organizationId: string; + + protected formGroup = this.formBuilder.group({ + enabled: [false], + }); + + protected showSecretsManager = false; + + constructor( + private formBuilder: FormBuilder, + private organizationApiService: OrganizationApiServiceAbstraction, + private platformUtilsService: PlatformUtilsService, + private i18nService: I18nService, + private syncService: SyncService + ) { + this.showSecretsManager = flagEnabled("secretsManager"); + } + + ngOnInit(): void { + this.formGroup.setValue({ + enabled: this.enabled, + }); + } + + protected submit = async () => { + this.formGroup.markAllAsTouched(); + + const request = new OrganizationEnrollSecretsManagerRequest(); + request.enabled = this.formGroup.value.enabled; + + await this.organizationApiService.updateEnrollSecretsManager(this.organizationId, request); + await this.syncService.fullSync(true); + this.platformUtilsService.showToast("success", null, this.i18nService.t("subscriptionUpdated")); + }; +} diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json index 06025f869d0..de2f1119f2a 100644 --- a/apps/web/src/locales/en/messages.json +++ b/apps/web/src/locales/en/messages.json @@ -6475,5 +6475,11 @@ }, "selectionIsRequired": { "message": "Selection is required." + }, + "secretsManagerSubscriptionDesc": { + "message": "Turn on organization access to the Secrets Manager at no charge during the Beta program. Users can be granted access to the Beta in Members." + }, + "secretsManagerEnable": { + "message": "Enable Secrets Manager Beta" } } diff --git a/libs/common/src/abstractions/organization/organization-api.service.abstraction.ts b/libs/common/src/abstractions/organization/organization-api.service.abstraction.ts index 6fe56db2269..f3aec0a72e8 100644 --- a/libs/common/src/abstractions/organization/organization-api.service.abstraction.ts +++ b/libs/common/src/abstractions/organization/organization-api.service.abstraction.ts @@ -11,6 +11,7 @@ import { OrganizationSubscriptionUpdateRequest } from "../../models/request/orga import { OrganizationTaxInfoUpdateRequest } from "../../models/request/organization-tax-info-update.request"; import { OrganizationUpdateRequest } from "../../models/request/organization-update.request"; import { OrganizationUpgradeRequest } from "../../models/request/organization-upgrade.request"; +import { OrganizationEnrollSecretsManagerRequest } from "../../models/request/organization/organization-enroll-secrets-manager.request"; import { PaymentRequest } from "../../models/request/payment.request"; import { SeatRequest } from "../../models/request/seat.request"; import { StorageRequest } from "../../models/request/storage.request"; @@ -59,4 +60,8 @@ export class OrganizationApiServiceAbstraction { getSso: (id: string) => Promise; updateSso: (id: string, request: OrganizationSsoRequest) => Promise; selfHostedSyncLicense: (id: string) => Promise; + updateEnrollSecretsManager: ( + id: string, + request: OrganizationEnrollSecretsManagerRequest + ) => Promise; } diff --git a/libs/common/src/models/request/organization/organization-enroll-secrets-manager.request.ts b/libs/common/src/models/request/organization/organization-enroll-secrets-manager.request.ts new file mode 100644 index 00000000000..a213b07bba7 --- /dev/null +++ b/libs/common/src/models/request/organization/organization-enroll-secrets-manager.request.ts @@ -0,0 +1,3 @@ +export class OrganizationEnrollSecretsManagerRequest { + enabled: boolean; +} diff --git a/libs/common/src/models/response/organization.response.ts b/libs/common/src/models/response/organization.response.ts index ce684e34521..9f7649646df 100644 --- a/libs/common/src/models/response/organization.response.ts +++ b/libs/common/src/models/response/organization.response.ts @@ -26,6 +26,7 @@ export class OrganizationResponse extends BaseResponse { use2fa: boolean; useApi: boolean; useResetPassword: boolean; + useSecretsManager: boolean; hasPublicAndPrivateKeys: boolean; constructor(response: any) { @@ -53,6 +54,7 @@ export class OrganizationResponse extends BaseResponse { this.use2fa = this.getResponseProperty("Use2fa"); this.useApi = this.getResponseProperty("UseApi"); this.useResetPassword = this.getResponseProperty("UseResetPassword"); + this.useSecretsManager = this.getResponseProperty("UseSecretsManager"); this.hasPublicAndPrivateKeys = this.getResponseProperty("HasPublicAndPrivateKeys"); } } diff --git a/libs/common/src/services/organization/organization-api.service.ts b/libs/common/src/services/organization/organization-api.service.ts index 1b86511a045..b5c02e7b08d 100644 --- a/libs/common/src/services/organization/organization-api.service.ts +++ b/libs/common/src/services/organization/organization-api.service.ts @@ -13,6 +13,7 @@ import { OrganizationSubscriptionUpdateRequest } from "../../models/request/orga import { OrganizationTaxInfoUpdateRequest } from "../../models/request/organization-tax-info-update.request"; import { OrganizationUpdateRequest } from "../../models/request/organization-update.request"; import { OrganizationUpgradeRequest } from "../../models/request/organization-upgrade.request"; +import { OrganizationEnrollSecretsManagerRequest } from "../../models/request/organization/organization-enroll-secrets-manager.request"; import { PaymentRequest } from "../../models/request/payment.request"; import { SeatRequest } from "../../models/request/seat.request"; import { StorageRequest } from "../../models/request/storage.request"; @@ -292,4 +293,14 @@ export class OrganizationApiService implements OrganizationApiServiceAbstraction false ); } + + async updateEnrollSecretsManager(id: string, request: OrganizationEnrollSecretsManagerRequest) { + await this.apiService.send( + "POST", + "/organizations/" + id + "/enroll-secrets-manager", + request, + true, + true + ); + } }