+
{{ "maxSeatLimit" | i18n }}
+
+ {{ "secretsManager" | i18n }}
+
+
{{ "selfHostingTitle" | i18n }}
diff --git a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts
index 667c88a5315..770645dae03 100644
--- a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts
+++ b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.ts
@@ -9,8 +9,7 @@ import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-conso
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { OrganizationApiKeyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
-import { PlanType } from "@bitwarden/common/billing/enums";
-import { BitwardenProductType } from "@bitwarden/common/billing/enums/bitwarden-product-type.enum";
+import { BitwardenProductType, PlanType } from "@bitwarden/common/billing/enums";
import { OrganizationSubscriptionResponse } from "@bitwarden/common/billing/models/response/organization-subscription.response";
import { BillingSubscriptionItemResponse } from "@bitwarden/common/billing/models/response/subscription.response";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
@@ -23,6 +22,7 @@ import {
BillingSyncApiKeyComponent,
BillingSyncApiModalData,
} from "./billing-sync-api-key.component";
+import { SecretsManagerSubscriptionOptions } from "./secrets-manager/sm-adjust-subscription.component";
@Component({
selector: "app-org-subscription-cloud",
@@ -38,6 +38,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
adjustStorageAdd = true;
showAdjustStorage = false;
hasBillingSyncToken: boolean;
+ showAdjustSecretsManager = false;
showSecretsManagerSubscribe = false;
@@ -113,15 +114,26 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
this.showSecretsManagerSubscribe =
this.userOrg.canEditSubscription &&
!this.userOrg.useSecretsManager &&
+ this.subscription != null &&
!this.subscription.cancelled &&
!this.subscriptionMarkedForCancel;
- // Remove next line when the sm-ga-billing flag is deleted
- this.showSecretsManagerSubscribe =
- this.showSecretsManagerSubscribe &&
- (await this.configService.getFeatureFlagBool(FeatureFlag.SecretsManagerBilling));
+ this.showAdjustSecretsManager =
+ this.userOrg.canEditSubscription &&
+ this.userOrg.useSecretsManager &&
+ this.subscription != null &&
+ this.sub.secretsManagerPlan?.hasAdditionalSeatsOption &&
+ !this.subscription.cancelled &&
+ !this.subscriptionMarkedForCancel;
this.loading = false;
+
+ // Remove the remaining lines when the sm-ga-billing flag is deleted
+ const smBillingEnabled = await this.configService.getFeatureFlagBool(
+ FeatureFlag.SecretsManagerBilling
+ );
+ this.showSecretsManagerSubscribe = this.showSecretsManagerSubscribe && smBillingEnabled;
+ this.showAdjustSecretsManager = this.showAdjustSecretsManager && smBillingEnabled;
}
get subscription() {
@@ -169,6 +181,19 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
return this.sub.seats;
}
+ get smOptions(): SecretsManagerSubscriptionOptions {
+ return {
+ seatCount: this.sub.smSeats,
+ seatLimit: this.sub.maxAutoscaleSmSeats,
+ seatPrice: this.sub.secretsManagerPlan.seatPrice,
+ serviceAccountLimit: this.sub.maxAutoscaleSmServiceAccounts,
+ serviceAccountCount: this.sub.smServiceAccounts,
+ interval: this.sub.secretsManagerPlan.isAnnual ? "year" : "month",
+ additionalServiceAccountPrice: this.sub.secretsManagerPlan.additionalPricePerServiceAccount,
+ baseServiceAccountCount: this.sub.secretsManagerPlan.baseServiceAccount,
+ };
+ }
+
get maxAutoscaleSeats() {
return this.sub.maxAutoscaleSeats;
}
diff --git a/apps/web/src/app/billing/organizations/secrets-manager/enroll.component.html b/apps/web/src/app/billing/organizations/secrets-manager/enroll.component.html
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/apps/web/src/app/billing/organizations/secrets-manager/sm-adjust-subscription.component.html b/apps/web/src/app/billing/organizations/secrets-manager/sm-adjust-subscription.component.html
new file mode 100644
index 00000000000..d3da809aad8
--- /dev/null
+++ b/apps/web/src/app/billing/organizations/secrets-manager/sm-adjust-subscription.component.html
@@ -0,0 +1,92 @@
+
diff --git a/apps/web/src/app/billing/organizations/secrets-manager/sm-adjust-subscription.component.ts b/apps/web/src/app/billing/organizations/secrets-manager/sm-adjust-subscription.component.ts
new file mode 100644
index 00000000000..0b27da37239
--- /dev/null
+++ b/apps/web/src/app/billing/organizations/secrets-manager/sm-adjust-subscription.component.ts
@@ -0,0 +1,168 @@
+import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
+import { FormBuilder, Validators } from "@angular/forms";
+import { Subject, takeUntil } from "rxjs";
+
+import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
+import { OrganizationSmSubscriptionUpdateRequest } from "@bitwarden/common/billing/models/request/organization-sm-subscription-update.request";
+import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
+import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
+
+export interface SecretsManagerSubscriptionOptions {
+ interval: "year" | "month";
+
+ /**
+ * The current number of seats the organization subscribes to.
+ */
+ seatCount: number;
+
+ /**
+ * Optional auto-scaling limit for the number of seats the organization can subscribe to.
+ */
+ seatLimit: number;
+
+ /**
+ * The price per seat for the subscription.
+ */
+ seatPrice: number;
+
+ /**
+ * The number of service accounts that are included in the base subscription.
+ */
+ baseServiceAccountCount: number;
+
+ /**
+ * The current number of additional service accounts the organization subscribes to.
+ */
+ serviceAccountCount: number;
+
+ /**
+ * Optional auto-scaling limit for the number of additional service accounts the organization can subscribe to.
+ */
+ serviceAccountLimit: number;
+
+ /**
+ * The price per additional service account for the subscription.
+ */
+ additionalServiceAccountPrice: number;
+}
+
+@Component({
+ selector: "app-sm-adjust-subscription",
+ templateUrl: "sm-adjust-subscription.component.html",
+})
+export class SecretsManagerAdjustSubscriptionComponent implements OnInit, OnDestroy {
+ @Input() organizationId: string;
+ @Input() options: SecretsManagerSubscriptionOptions;
+ @Output() onAdjusted = new EventEmitter();
+
+ private destroy$ = new Subject
();
+
+ formGroup = this.formBuilder.group({
+ seatCount: [0, [Validators.required, Validators.min(1)]],
+ limitSeats: [false],
+ seatLimit: [null as number | null],
+ serviceAccountCount: [0, [Validators.required, Validators.min(0)]],
+ limitServiceAccounts: [false],
+ serviceAccountLimit: [null as number | null],
+ });
+
+ get monthlyServiceAccountPrice(): number {
+ return this.options.interval == "month"
+ ? this.options.additionalServiceAccountPrice
+ : this.options.additionalServiceAccountPrice / 12;
+ }
+
+ get serviceAccountTotal(): number {
+ return Math.abs(
+ this.formGroup.value.serviceAccountCount * this.options.additionalServiceAccountPrice
+ );
+ }
+
+ get seatTotal(): number {
+ return Math.abs(this.formGroup.value.seatCount * this.options.seatPrice);
+ }
+
+ get maxServiceAccountTotal(): number {
+ return Math.abs(
+ (this.formGroup.value.serviceAccountLimit ?? 0) * this.options.additionalServiceAccountPrice
+ );
+ }
+
+ get maxSeatTotal(): number {
+ return Math.abs((this.formGroup.value.seatLimit ?? 0) * this.options.seatPrice);
+ }
+
+ constructor(
+ private formBuilder: FormBuilder,
+ private organizationApiService: OrganizationApiServiceAbstraction,
+ private i18nService: I18nService,
+ private platformUtilsService: PlatformUtilsService
+ ) {}
+
+ ngOnInit() {
+ this.formGroup.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((value) => {
+ const seatLimitControl = this.formGroup.controls.seatLimit;
+ const serviceAccountLimitControl = this.formGroup.controls.serviceAccountLimit;
+
+ if (value.limitSeats) {
+ seatLimitControl.setValidators([Validators.min(value.seatCount)]);
+ seatLimitControl.enable({ emitEvent: false });
+ } else {
+ seatLimitControl.disable({ emitEvent: false });
+ }
+
+ if (value.limitServiceAccounts) {
+ serviceAccountLimitControl.setValidators([Validators.min(value.serviceAccountCount)]);
+ serviceAccountLimitControl.enable({ emitEvent: false });
+ } else {
+ serviceAccountLimitControl.disable({ emitEvent: false });
+ }
+ });
+
+ this.formGroup.patchValue({
+ seatCount: this.options.seatCount,
+ seatLimit: this.options.seatLimit,
+ serviceAccountCount: this.options.serviceAccountCount,
+ serviceAccountLimit: this.options.serviceAccountLimit,
+ limitSeats: this.options.seatLimit != null,
+ limitServiceAccounts: this.options.serviceAccountLimit != null,
+ });
+ }
+
+ submit = async () => {
+ this.formGroup.markAllAsTouched();
+
+ if (this.formGroup.invalid) {
+ return;
+ }
+
+ const seatAdjustment = this.formGroup.value.seatCount - this.options.seatCount;
+ const serviceAccountAdjustment =
+ this.formGroup.value.serviceAccountCount - this.options.serviceAccountCount;
+
+ const request = new OrganizationSmSubscriptionUpdateRequest(
+ seatAdjustment,
+ serviceAccountAdjustment,
+ this.formGroup.value.seatLimit,
+ this.formGroup.value.serviceAccountLimit
+ );
+
+ await this.organizationApiService.updateSecretsManagerSubscription(
+ this.organizationId,
+ request
+ );
+
+ await this.platformUtilsService.showToast(
+ "success",
+ null,
+ this.i18nService.t("subscriptionUpdated")
+ );
+
+ this.onAdjusted.emit();
+ };
+
+ ngOnDestroy() {
+ this.destroy$.next();
+ this.destroy$.complete();
+ }
+}
diff --git a/apps/web/src/app/billing/organizations/secrets-manager/sm-billing.module.ts b/apps/web/src/app/billing/organizations/secrets-manager/sm-billing.module.ts
index a46286fc5a3..127a6e49fe6 100644
--- a/apps/web/src/app/billing/organizations/secrets-manager/sm-billing.module.ts
+++ b/apps/web/src/app/billing/organizations/secrets-manager/sm-billing.module.ts
@@ -2,12 +2,21 @@ import { NgModule } from "@angular/core";
import { SharedModule } from "../../../shared";
+import { SecretsManagerAdjustSubscriptionComponent } from "./sm-adjust-subscription.component";
import { SecretsManagerSubscribeStandaloneComponent } from "./sm-subscribe-standalone.component";
import { SecretsManagerSubscribeComponent } from "./sm-subscribe.component";
@NgModule({
imports: [SharedModule],
- declarations: [SecretsManagerSubscribeComponent, SecretsManagerSubscribeStandaloneComponent],
- exports: [SecretsManagerSubscribeComponent, SecretsManagerSubscribeStandaloneComponent],
+ declarations: [
+ SecretsManagerSubscribeComponent,
+ SecretsManagerSubscribeStandaloneComponent,
+ SecretsManagerAdjustSubscriptionComponent,
+ ],
+ exports: [
+ SecretsManagerSubscribeComponent,
+ SecretsManagerSubscribeStandaloneComponent,
+ SecretsManagerAdjustSubscriptionComponent,
+ ],
})
export class SecretsManagerBillingModule {}
diff --git a/apps/web/src/app/billing/settings/organization-plans.component.ts b/apps/web/src/app/billing/settings/organization-plans.component.ts
index 23a38c654a9..7d9f76708ea 100644
--- a/apps/web/src/app/billing/settings/organization-plans.component.ts
+++ b/apps/web/src/app/billing/settings/organization-plans.component.ts
@@ -20,8 +20,7 @@ import { OrganizationCreateRequest } from "@bitwarden/common/admin-console/model
import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request";
import { OrganizationUpgradeRequest } from "@bitwarden/common/admin-console/models/request/organization-upgrade.request";
import { ProviderOrganizationCreateRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-organization-create.request";
-import { PaymentMethodType, PlanType } from "@bitwarden/common/billing/enums";
-import { BitwardenProductType } from "@bitwarden/common/billing/enums/bitwarden-product-type";
+import { BitwardenProductType, PaymentMethodType, PlanType } from "@bitwarden/common/billing/enums";
import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response";
import { ProductType } from "@bitwarden/common/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
@@ -56,24 +55,29 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy {
@Input() showFree = true;
@Input() showCancel = false;
@Input() acceptingSponsorship = false;
+
@Input()
get product(): ProductType {
return this._product;
}
+
set product(product: ProductType) {
this._product = product;
this.formGroup?.controls?.product?.setValue(product);
}
+
private _product = ProductType.Free;
@Input()
get plan(): PlanType {
return this._plan;
}
+
set plan(plan: PlanType) {
this._plan = plan;
this.formGroup?.controls?.plan?.setValue(plan);
}
+
private _plan = PlanType.Free;
@Input() providerId?: string;
@Output() onSuccess = new EventEmitter();
diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json
index b882f5ef889..ff24551f891 100644
--- a/apps/web/src/locales/en/messages.json
+++ b/apps/web/src/locales/en/messages.json
@@ -404,8 +404,7 @@
"viewItem": {
"message": "View item"
},
- "new":
- {
+ "new": {
"message": "New",
"description": "for adding new items"
},
@@ -3324,6 +3323,9 @@
"limitSubscriptionDesc": {
"message": "Set a seat limit for your subscription. Once this limit is reached, you will not be able to invite new members."
},
+ "limitSmSubscriptionDesc": {
+ "message": "Set a seat limit for your Secrets Manger subscription. Once this limit is reached, you will not be able to invite new members."
+ },
"maxSeatLimit": {
"message": "Seat Limit (optional)",
"description": "Upper limit of seats to allow through autoscaling"
@@ -5855,10 +5857,10 @@
"message": "Delete secrets",
"description": "The action to delete multiple secrets from the system."
},
- "hardDeleteSecret":{
+ "hardDeleteSecret": {
"message": "Permanently delete secret"
},
- "hardDeleteSecrets":{
+ "hardDeleteSecrets": {
"message": "Permanently delete secrets"
},
"secretProjectAssociationDescription": {
@@ -5937,14 +5939,14 @@
"message": "To get started, add a new secret or import secrets.",
"description": "Message to encourage the user to start adding secrets."
},
- "secretsTrashNoItemsMessage":{
+ "secretsTrashNoItemsMessage": {
"message": "There are no secrets in the trash."
},
"serviceAccountsNoItemsMessage": {
"message": "Create a new service account to get started automating secret access.",
"description": "Message to encourage the user to start creating service accounts."
},
- "serviceAccountsNoItemsTitle": {
+ "serviceAccountsNoItemsTitle": {
"message": "Nothing to show yet",
"description": "Title to indicate that there are no service accounts to display."
},
@@ -5965,7 +5967,7 @@
"description": "Action to view the details of a service account."
},
"deleteServiceAccountDialogMessage": {
- "message": "Deleting service account $SERVICE_ACCOUNT$ is permanent and irreversible.",
+ "message": "Deleting service account $SERVICE_ACCOUNT$ is permanent and irreversible.",
"placeholders": {
"service_account": {
"content": "$1",
@@ -5973,11 +5975,11 @@
}
}
},
- "deleteServiceAccountsDialogMessage":{
+ "deleteServiceAccountsDialogMessage": {
"message": "Deleting service accounts is permanent and irreversible."
},
- "deleteServiceAccountsConfirmMessage":{
- "message": "Delete $COUNT$ service accounts",
+ "deleteServiceAccountsConfirmMessage": {
+ "message": "Delete $COUNT$ service accounts",
"placeholders": {
"count": {
"content": "$1",
@@ -5985,19 +5987,19 @@
}
}
},
- "deleteServiceAccountToast":{
+ "deleteServiceAccountToast": {
"message": "Service account deleted"
},
- "deleteServiceAccountsToast":{
+ "deleteServiceAccountsToast": {
"message": "Service accounts deleted"
},
"searchServiceAccounts": {
"message": "Search service accounts",
"description": "Placeholder text for searching service accounts."
},
- "editServiceAccount":{
- "message":"Edit service account",
- "description" : "Title for editing a service account."
+ "editServiceAccount": {
+ "message": "Edit service account",
+ "description": "Title for editing a service account."
},
"addProject": {
"message": "Add project",
@@ -6037,8 +6039,8 @@
"hardDeleteSecretsConfirmation": {
"message": "Are you sure you want to permanently delete these secrets?"
},
- "hardDeletesSuccessToast":{
- "message":"Secrets permanently deleted"
+ "hardDeletesSuccessToast": {
+ "message": "Secrets permanently deleted"
},
"smAccess": {
"message": "Access",
@@ -6052,7 +6054,7 @@
"message": "Service account name",
"description": "Label for the name of a service account"
},
- "serviceAccountCreated": {
+ "serviceAccountCreated": {
"message": "Service account created",
"description": "Notifies that a new service account has been created"
},
@@ -6140,8 +6142,8 @@
"message": "Secret sent to trash",
"description": "Notification to be displayed when a secret is successfully sent to the trash."
},
- "hardDeleteSuccessToast":{
- "message":"Secret permanently deleted"
+ "hardDeleteSuccessToast": {
+ "message": "Secret permanently deleted"
},
"accessTokens": {
"message": "Access tokens",
@@ -6844,8 +6846,8 @@
"message": "with automatic enrollment will turn on when this option is used.",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The master password reset policy with automatic enrollment will turn on when this option is used.'"
},
- "notFound":{
- "message": "$RESOURCE$ not found",
+ "notFound": {
+ "message": "$RESOURCE$ not found",
"placeholders": {
"resource": {
"content": "$1",
@@ -6998,6 +7000,17 @@
},
"freeOrganization": {
"message": "Free Organization"
+ },
+ "limitServiceAccounts": {
+ "message": "Limit service accounts (optional)"
+ },
+ "limitServiceAccountsDesc": {
+ "message": "Set a limit for your service accounts. Once this limit is reached, you will not be able to create new service accounts."
+ },
+ "serviceAccountLimit": {
+ "message": "Service account limit (optional)"
+ },
+ "maxServiceAccountCost": {
+ "message": "Max potential service account cost"
}
}
-
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 c6ce62f232b..b6778cf61e6 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
@@ -3,6 +3,7 @@ import { OrganizationSsoRequest } from "../../../auth/models/request/organizatio
import { SecretVerificationRequest } from "../../../auth/models/request/secret-verification.request";
import { ApiKeyResponse } from "../../../auth/models/response/api-key.response";
import { OrganizationSsoResponse } from "../../../auth/models/response/organization-sso.response";
+import { OrganizationSmSubscriptionUpdateRequest } from "../../../billing/models/request/organization-sm-subscription-update.request";
import { OrganizationSubscriptionUpdateRequest } from "../../../billing/models/request/organization-subscription-update.request";
import { OrganizationTaxInfoUpdateRequest } from "../../../billing/models/request/organization-tax-info-update.request";
import { PaymentRequest } from "../../../billing/models/request/payment.request";
@@ -41,6 +42,10 @@ export class OrganizationApiServiceAbstraction {
id: string,
request: OrganizationSubscriptionUpdateRequest
) => Promise;
+ updateSecretsManagerSubscription: (
+ id: string,
+ request: OrganizationSmSubscriptionUpdateRequest
+ ) => Promise;
updateSeats: (id: string, request: SeatRequest) => Promise;
updateStorage: (id: string, request: StorageRequest) => Promise;
verifyBank: (id: string, request: VerifyBankRequest) => Promise;
diff --git a/libs/common/src/admin-console/models/response/organization.response.ts b/libs/common/src/admin-console/models/response/organization.response.ts
index 6bd339a64de..b248c6d0df9 100644
--- a/libs/common/src/admin-console/models/response/organization.response.ts
+++ b/libs/common/src/admin-console/models/response/organization.response.ts
@@ -28,6 +28,11 @@ export class OrganizationResponse extends BaseResponse {
useResetPassword: boolean;
useSecretsManager: boolean;
hasPublicAndPrivateKeys: boolean;
+ usePasswordManager: boolean;
+ smSeats?: number;
+ smServiceAccounts?: number;
+ maxAutoscaleSmSeats?: number;
+ maxAutoscaleSmServiceAccounts?: number;
constructor(response: any) {
super(response);
@@ -62,5 +67,10 @@ export class OrganizationResponse extends BaseResponse {
this.useResetPassword = this.getResponseProperty("UseResetPassword");
this.useSecretsManager = this.getResponseProperty("UseSecretsManager");
this.hasPublicAndPrivateKeys = this.getResponseProperty("HasPublicAndPrivateKeys");
+ this.usePasswordManager = this.getResponseProperty("UsePasswordManager");
+ this.smSeats = this.getResponseProperty("SmSeats");
+ this.smServiceAccounts = this.getResponseProperty("SmServiceAccounts");
+ this.maxAutoscaleSmSeats = this.getResponseProperty("MaxAutoscaleSmSeats");
+ this.maxAutoscaleSmServiceAccounts = this.getResponseProperty("MaxAutoscaleSmServiceAccounts");
}
}
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 503aeb3820f..c235b4b8eea 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
@@ -4,6 +4,7 @@ import { OrganizationSsoRequest } from "../../../auth/models/request/organizatio
import { SecretVerificationRequest } from "../../../auth/models/request/secret-verification.request";
import { ApiKeyResponse } from "../../../auth/models/response/api-key.response";
import { OrganizationSsoResponse } from "../../../auth/models/response/organization-sso.response";
+import { OrganizationSmSubscriptionUpdateRequest } from "../../../billing/models/request/organization-sm-subscription-update.request";
import { OrganizationSubscriptionUpdateRequest } from "../../../billing/models/request/organization-subscription-update.request";
import { OrganizationTaxInfoUpdateRequest } from "../../../billing/models/request/organization-tax-info-update.request";
import { PaymentRequest } from "../../../billing/models/request/payment.request";
@@ -133,6 +134,19 @@ export class OrganizationApiService implements OrganizationApiServiceAbstraction
);
}
+ async updateSecretsManagerSubscription(
+ id: string,
+ request: OrganizationSmSubscriptionUpdateRequest
+ ): Promise {
+ return this.apiService.send(
+ "POST",
+ "/organizations/" + id + "/sm-subscription",
+ request,
+ true,
+ false
+ );
+ }
+
async updateSeats(id: string, request: SeatRequest): Promise {
const r = await this.apiService.send(
"POST",
diff --git a/libs/common/src/billing/enums/bitwarden-product-type.ts b/libs/common/src/billing/enums/bitwarden-product-type.ts
deleted file mode 100644
index 76b0899fd9c..00000000000
--- a/libs/common/src/billing/enums/bitwarden-product-type.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export enum BitwardenProductType {
- PasswordManager = 0,
- SecretsManager = 1,
-}
diff --git a/libs/common/src/billing/enums/index.ts b/libs/common/src/billing/enums/index.ts
index b4f96cd8fd6..70a3495a8bf 100644
--- a/libs/common/src/billing/enums/index.ts
+++ b/libs/common/src/billing/enums/index.ts
@@ -2,3 +2,4 @@ export * from "./payment-method-type.enum";
export * from "./plan-sponsorship-type.enum";
export * from "./plan-type.enum";
export * from "./transaction-type.enum";
+export * from "./bitwarden-product-type.enum";
diff --git a/libs/common/src/billing/models/request/organization-sm-subscription-update.request.ts b/libs/common/src/billing/models/request/organization-sm-subscription-update.request.ts
new file mode 100644
index 00000000000..a69937d2311
--- /dev/null
+++ b/libs/common/src/billing/models/request/organization-sm-subscription-update.request.ts
@@ -0,0 +1,40 @@
+export class OrganizationSmSubscriptionUpdateRequest {
+ /**
+ * The number of seats to add or remove from the subscription.
+ */
+ seatAdjustment: number;
+
+ /**
+ * The maximum number of seats that can be auto-scaled for the subscription.
+ */
+ maxAutoscaleSeats?: number;
+
+ /**
+ * The number of additional service accounts to add or remove from the subscription.
+ */
+ serviceAccountAdjustment: number;
+
+ /**
+ * The maximum number of additional service accounts that can be auto-scaled for the subscription.
+ */
+ maxAutoscaleServiceAccounts?: number;
+
+ /**
+ * Build a subscription update request for the Secrets Manager product type.
+ * @param seatAdjustment - The number of seats to add or remove from the subscription.
+ * @param serviceAccountAdjustment - The number of additional service accounts to add or remove from the subscription.
+ * @param maxAutoscaleSeats - The maximum number of seats that can be auto-scaled for the subscription.
+ * @param maxAutoscaleServiceAccounts - The maximum number of additional service accounts that can be auto-scaled for the subscription.
+ */
+ constructor(
+ seatAdjustment: number,
+ serviceAccountAdjustment: number,
+ maxAutoscaleSeats?: number,
+ maxAutoscaleServiceAccounts?: number
+ ) {
+ this.seatAdjustment = seatAdjustment;
+ this.serviceAccountAdjustment = serviceAccountAdjustment;
+ this.maxAutoscaleSeats = maxAutoscaleSeats;
+ this.maxAutoscaleServiceAccounts = maxAutoscaleServiceAccounts;
+ }
+}
diff --git a/libs/common/src/billing/models/request/organization-subscription-update.request.ts b/libs/common/src/billing/models/request/organization-subscription-update.request.ts
index 9db3d4be80e..d7566806f6b 100644
--- a/libs/common/src/billing/models/request/organization-subscription-update.request.ts
+++ b/libs/common/src/billing/models/request/organization-subscription-update.request.ts
@@ -1,3 +1,23 @@
export class OrganizationSubscriptionUpdateRequest {
- constructor(public seatAdjustment: number, public maxAutoscaleSeats?: number) {}
+ /**
+ * The number of seats to add or remove from the subscription.
+ * Applies to both PM and SM request types.
+ */
+ seatAdjustment: number;
+
+ /**
+ * The maximum number of seats that can be auto-scaled for the subscription.
+ * Applies to both PM and SM request types.
+ */
+ maxAutoscaleSeats?: number;
+
+ /**
+ * Build a subscription update request for the Password Manager product type.
+ * @param seatAdjustment - The number of seats to add or remove from the subscription.
+ * @param maxAutoscaleSeats - The maximum number of seats that can be auto-scaled for the subscription.
+ */
+ constructor(seatAdjustment: number, maxAutoscaleSeats?: number) {
+ this.seatAdjustment = seatAdjustment;
+ this.maxAutoscaleSeats = maxAutoscaleSeats;
+ }
}
diff --git a/libs/common/src/billing/models/response/plan.response.ts b/libs/common/src/billing/models/response/plan.response.ts
index e67ed35e44b..c3558a1b534 100644
--- a/libs/common/src/billing/models/response/plan.response.ts
+++ b/libs/common/src/billing/models/response/plan.response.ts
@@ -1,7 +1,6 @@
import { ProductType } from "../../../enums";
import { BaseResponse } from "../../../models/response/base.response";
-import { PlanType } from "../../enums";
-import { BitwardenProductType } from "../../enums/bitwarden-product-type";
+import { BitwardenProductType, PlanType } from "../../enums";
export class PlanResponse extends BaseResponse {
type: PlanType;
diff --git a/libs/common/src/billing/models/response/subscription.response.ts b/libs/common/src/billing/models/response/subscription.response.ts
index f966a3e9bfa..ffcc9c23768 100644
--- a/libs/common/src/billing/models/response/subscription.response.ts
+++ b/libs/common/src/billing/models/response/subscription.response.ts
@@ -1,5 +1,5 @@
import { BaseResponse } from "../../../models/response/base.response";
-import { BitwardenProductType } from "../../enums/bitwarden-product-type.enum";
+import { BitwardenProductType } from "../../enums";
export class SubscriptionResponse extends BaseResponse {
storageName: string;