mirror of
https://github.com/bitwarden/browser.git
synced 2025-02-22 02:21:34 +01:00
[AC-1512] Feature: Secrets Manager billing - round 2 (#5854)
* [AC-1423] Update organization subscription cloud page (#5614) * [AC-1423] Add ProgressModule to shared.module.ts * [AC-1423] Update cloud subscription page styles - Remove bootstrap styles - Use CL components where applicable - Use CL typography directives - Update heading levels to prepare for new SM sections * [AC-1423] Add usePasswordManager boolean to organization domain * [AC-1423] Introduce BitwardenProductType enum * [AC-1423] Update Organization subscription line items - Add product type prefix - Indent addon services like additional storage and service accounts - Show line items for free plans * [AC-1423] Simply sort function * [AC-1423] Remove header border * [AC-1423] Make "Password Manager" the default fallback for product name * [AC-1420] Add Secrets Manager subscribe component (#5617) * [AC-1418] Add secrets manager manage subscription component (#5661) * [AC-1423] Add minWidth input to bit-progress component * [AC-1423] Add ProgressModule to shared.module.ts * [AC-1423] Update cloud subscription page styles - Remove bootstrap styles - Use CL components where applicable - Use CL typography directives - Update heading levels to prepare for new SM sections * [AC-1423] Add usePasswordManager boolean to organization domain * [AC-1423] Introduce BitwardenProductType enum * [AC-1423] Update Organization subscription line items - Add product type prefix - Indent addon services like additional storage and service accounts - Show line items for free plans * [AC-1423] Simply sort function * [AC-1423] Remove header border * [AC-1423] Remove redundant condition * [AC-1423] Remove ineffective div * [AC-1423] Make "Password Manager" the default fallback for product name * Revert "[AC-1423] Add minWidth input to bit-progress component" This reverts commit95b2223a30
. * [AC-1423] Remove minWidth attribute * [AC-1423] Switch to AddonProductType enum instead of boolean * Revert "[AC-1423] Switch to AddonProductType enum instead of boolean" This reverts commit204f64b4e7
. * [AC-1423] Tweak sorting comment * [AC-1418] Add initial SecretsManagerAdjustSubscription component * [AC-1418] Add initial SM adjustment form * [AC-1418] Adjust organization-subscription-update.request.ts to support both PM and SM * [AC-1418] Rename service account fields in the options interface * [AC-1418] Add api service call to update SM subscription * [AC-1418] Cleanup form html * [AC-1418] Add missing SM plan properties * [AC-1418] Add SM subscription adjust form and logic to hide it * [AC-1418] Add better docs to options interface * [AC-1418] Fix conflicting required/optional labels for auto-scaling limits * [AC-1418] Adjust labels and appearance to better match design * [AC-1418] Use the SM plan for billing interval * [AC-1418] Hide SM billing adjustment component behind feature flag * [AC-1418] Update request model to match server * [AC-1418] Cleanup BitwardenProductType after merge Add to barrel file and update applicable imports. * [AC-1418] Revert change to update PM subscription request model * [AC-1418] Add new update SM subscription request model * [AC-1418] Add new service method to update SM subscription * [AC-1418] Use new model and service method * [AC-1418] Cleanup SM subscription UI flags * [AC-1418] Move SM adjust subscription component into SM billing module * [AC-1418] Update SM seat count minimum to 1 * [AC-1418] Add missing currency codes * [AC-1418] Simplify monthly price calculation * [AC-1418] Increase PM adjust subscription form input width * [AC-1418] Add check for null subscription --------- Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> * add the additional properties (#5743) * Allow autoscale limits to be removed, update naming (#5781) * [AC-1488] Store Organization.SmServiceAccounts as total not additional (#5784) * Allow autoscale limits to be removed, update naming * Display additional service accounts only --------- Co-authored-by: Shane Melton <smelton@bitwarden.com> * [AC-1473] SM beta ending callout (#5719) * [AC-1423] Add minWidth input to bit-progress component * [AC-1423] Add ProgressModule to shared.module.ts * [AC-1423] Update cloud subscription page styles - Remove bootstrap styles - Use CL components where applicable - Use CL typography directives - Update heading levels to prepare for new SM sections * [AC-1423] Add usePasswordManager boolean to organization domain * [AC-1423] Introduce BitwardenProductType enum * [AC-1423] Update Organization subscription line items - Add product type prefix - Indent addon services like additional storage and service accounts - Show line items for free plans * [AC-1423] Simply sort function * [AC-1423] Remove header border * [AC-1423] Remove redundant condition * [AC-1423] Remove ineffective div * [AC-1423] Make "Password Manager" the default fallback for product name * Revert "[AC-1423] Add minWidth input to bit-progress component" This reverts commit95b2223a30
. * [AC-1423] Remove minWidth attribute * [AC-1423] Switch to AddonProductType enum instead of boolean * Revert "[AC-1423] Switch to AddonProductType enum instead of boolean" This reverts commit204f64b4e7
. * [AC-1423] Tweak sorting comment * [AC-1418] Add initial SecretsManagerAdjustSubscription component * [AC-1418] Add initial SM adjustment form * [AC-1418] Adjust organization-subscription-update.request.ts to support both PM and SM * [AC-1418] Rename service account fields in the options interface * [AC-1418] Add api service call to update SM subscription * [AC-1418] Cleanup form html * [AC-1418] Add missing SM plan properties * [AC-1418] Add SM subscription adjust form and logic to hide it * [AC-1418] Add better docs to options interface * [AC-1418] Fix conflicting required/optional labels for auto-scaling limits * [AC-1418] Adjust labels and appearance to better match design * [AC-1418] Use the SM plan for billing interval * [AC-1418] Hide SM billing adjustment component behind feature flag * [AC-1418] Update request model to match server * [AC-1418] Cleanup BitwardenProductType after merge Add to barrel file and update applicable imports. * [AC-1418] Revert change to update PM subscription request model * [AC-1418] Add new update SM subscription request model * [AC-1418] Add new service method to update SM subscription * [AC-1418] Use new model and service method * [AC-1418] Cleanup SM subscription UI flags * [AC-1418] Move SM adjust subscription component into SM billing module * [AC-1418] Update SM seat count minimum to 1 * [AC-1418] Add missing currency codes * [AC-1418] Simplify monthly price calculation * add daysRemaining util function and unit tests * [AC-1474] update organization models to include SM beta flag * add SM beta callout to org subscription page * update messages.json * remove beta field from profile org response * improve daysRemaining code clarity * set SM beta in org model constructor * tweak free SM row visibility * refactor callout description * Revert "remove beta field from profile org response" This reverts commit6c6249e1ec
. * fix dates * [AC-1468]: hide adjust SM component if beta user * add sm beta field to org sub response; remove everywhere else * fix copy --------- Co-authored-by: Shane Melton <smelton@bitwarden.com> Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> Co-authored-by: Thomas Rittson <trittson@bitwarden.com> * [AC-1531] Fix SM subscribe component not showing in free org billing tab (#5848) Also: * Fix spacing in layout * Send zero values for free plans * Fix: properly delete enroll component * remove the beta end message for free org (#5877) * [AC-1458] Update local organization data after subscribing to Secrets Manager (#5888) * [AC-1567] Fix max additional service account cost estimate (#5923) * Fix max additional service account cost estimate * Update i18n string ref * Make i18n string keys consistent * [AC-1461] Secrets Manager seat autoscaling cleanup (#5924) * Remove unused return value from putOrganizationUserBulkEnableSecretsManager * Fix service account limit validator (#5926) * Updated Utils.daysRemaining method to calculate result using Math.floor and updated unit tests. --------- Co-authored-by: Shane Melton <smelton@bitwarden.com> Co-authored-by: cyprain-okeke <108260115+cyprain-okeke@users.noreply.github.com> Co-authored-by: Rui Tome <rtome@bitwarden.com> Co-authored-by: Will Martin <contact@willmartian.com> Co-authored-by: Álison Fernandes <vvolkgang@users.noreply.github.com> Co-authored-by: Rui Tomé <108268980+r-tome@users.noreply.github.com>
This commit is contained in:
parent
dad6fedebd
commit
b89f31101f
@ -9,7 +9,7 @@ import { SettingsService as SettingsServiceAbstraction } from "@bitwarden/common
|
||||
import { TotpService as TotpServiceAbstraction } from "@bitwarden/common/abstractions/totp.service";
|
||||
import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
|
||||
import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service";
|
||||
import { InternalOrganizationService as InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
|
||||
import { InternalPolicyService as InternalPolicyServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { ProviderService as ProviderServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/provider.service";
|
||||
|
@ -87,13 +87,32 @@
|
||||
<td bitCell>{{ "passwordManager" | i18n }} - {{ "freeOrganization" | i18n }}</td>
|
||||
<td bitCell class="tw-text-right">{{ "free" | i18n }}</td>
|
||||
</tr>
|
||||
<tr bitRow *ngIf="userOrg.useSecretsManager">
|
||||
<tr bitRow *ngIf="userOrg.useSecretsManager && !sub.secretsManagerBeta">
|
||||
<td bitCell>{{ "secretsManager" | i18n }} - {{ "freeOrganization" | i18n }}</td>
|
||||
<td bitCell class="tw-text-right">{{ "free" | i18n }}</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<tr bitRow *ngIf="sub.secretsManagerBeta">
|
||||
<td bitCell>
|
||||
{{ "secretsManager" | i18n }} -
|
||||
{{ "beta" | i18n }}
|
||||
({{ "annually" | i18n }}) @
|
||||
{{ 0 | currency : "$" }}
|
||||
<span bitBadge badgeType="warning" class="tw-ml-2">{{
|
||||
"betaEnding" | i18n | uppercase
|
||||
}}</span>
|
||||
</td>
|
||||
<td bitCell class="tw-text-right">{{ 0 | currency : "$" }} /{{ "year" | i18n }}</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
</bit-table>
|
||||
<bit-callout
|
||||
*ngIf="sub.secretsManagerBeta && !userOrg.isFreeOrg"
|
||||
type="warning"
|
||||
class="tw-mt-4 tw-block"
|
||||
>
|
||||
{{ smBetaEndedDesc }}
|
||||
</bit-callout>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { DatePipe } from "@angular/common";
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { concatMap, Subject, takeUntil } from "rxjs";
|
||||
@ -17,6 +18,7 @@ import { ConfigServiceAbstraction } from "@bitwarden/common/platform/abstraction
|
||||
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 { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
|
||||
import {
|
||||
BillingSyncApiKeyComponent,
|
||||
@ -45,6 +47,9 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
|
||||
firstLoaded = false;
|
||||
loading: boolean;
|
||||
|
||||
private readonly _smBetaEndingDate = new Date(2023, 7, 25);
|
||||
private readonly _smGracePeriodEndingDate = new Date(2023, 9, 24);
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
@ -57,7 +62,8 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private route: ActivatedRoute,
|
||||
private dialogService: DialogServiceAbstraction,
|
||||
private configService: ConfigServiceAbstraction
|
||||
private configService: ConfigServiceAbstraction,
|
||||
private datePipe: DatePipe
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
@ -122,6 +128,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
|
||||
this.userOrg.useSecretsManager &&
|
||||
this.subscription != null &&
|
||||
this.sub.secretsManagerPlan?.hasAdditionalSeatsOption &&
|
||||
!this.sub.secretsManagerBeta &&
|
||||
!this.subscription.cancelled &&
|
||||
!this.subscriptionMarkedForCancel;
|
||||
|
||||
@ -256,6 +263,14 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
|
||||
);
|
||||
}
|
||||
|
||||
get smBetaEndedDesc() {
|
||||
return this.i18nService.translate(
|
||||
"smBetaEndedDesc",
|
||||
this.datePipe.transform(this._smBetaEndingDate),
|
||||
Utils.daysRemaining(this._smGracePeriodEndingDate).toString()
|
||||
);
|
||||
}
|
||||
|
||||
cancel = async () => {
|
||||
if (this.loading) {
|
||||
return;
|
||||
|
@ -5,7 +5,7 @@
|
||||
<bit-hint>
|
||||
<strong>{{ "total" | i18n }}:</strong>
|
||||
{{ formGroup.value.seatCount || 0 }} × {{ options.seatPrice | currency : "$" }} =
|
||||
{{ seatTotal | currency : "$" }} / {{ options.interval | i18n }}
|
||||
{{ seatTotalCost | currency : "$" }} / {{ options.interval | i18n }}
|
||||
</bit-hint>
|
||||
</bit-form-field>
|
||||
<bit-form-control>
|
||||
@ -28,7 +28,7 @@
|
||||
<bit-hint>
|
||||
<strong>{{ "maxSeatCost" | i18n }}:</strong>
|
||||
{{ formGroup.value.maxAutoscaleSeats || 0 }} ×
|
||||
{{ options.seatPrice | currency : "$" }} = {{ maxSeatTotal | currency : "$" }} /
|
||||
{{ options.seatPrice | currency : "$" }} = {{ maxSeatTotalCost | currency : "$" }} /
|
||||
{{ options.interval | i18n }}
|
||||
</bit-hint>
|
||||
</bit-form-field>
|
||||
@ -44,16 +44,14 @@
|
||||
/>
|
||||
<bit-hint>
|
||||
<div>
|
||||
{{
|
||||
"additionalServiceAccountsDesc"
|
||||
| i18n : options.baseServiceAccountCount : (monthlyServiceAccountPrice | currency : "$")
|
||||
}}
|
||||
{{ "includedServiceAccounts" | i18n : options.baseServiceAccountCount }}
|
||||
{{ "addAdditionalServiceAccounts" | i18n : (monthlyServiceAccountPrice | currency : "$") }}
|
||||
</div>
|
||||
<div>
|
||||
<strong>{{ "total" | i18n }}:</strong>
|
||||
{{ formGroup.value.additionalServiceAccounts || 0 }} ×
|
||||
{{ options.additionalServiceAccountPrice | currency : "$" }} =
|
||||
{{ serviceAccountTotal | currency : "$" }} / {{ options.interval | i18n }}
|
||||
{{ serviceAccountTotalCost | currency : "$" }} / {{ options.interval | i18n }}
|
||||
</div>
|
||||
</bit-hint>
|
||||
</bit-form-field>
|
||||
@ -80,10 +78,13 @@
|
||||
[min]="formGroup.value.additionalServiceAccounts"
|
||||
/>
|
||||
<bit-hint>
|
||||
<div>
|
||||
{{ "includedServiceAccounts" | i18n : options.baseServiceAccountCount }}
|
||||
</div>
|
||||
<strong>{{ "maxServiceAccountCost" | i18n }}:</strong>
|
||||
{{ formGroup.value.maxAutoscaleServiceAccounts || 0 }} ×
|
||||
{{ maxAdditionalServiceAccounts }} ×
|
||||
{{ options.additionalServiceAccountPrice | currency : "$" }} =
|
||||
{{ maxServiceAccountTotal | currency : "$" }} / {{ options.interval | i18n }}
|
||||
{{ maxServiceAccountTotalCost | currency : "$" }} / {{ options.interval | i18n }}
|
||||
</bit-hint>
|
||||
</bit-form-field>
|
||||
<button type="submit" bitButton buttonType="primary" bitFormButton>
|
||||
|
@ -72,24 +72,26 @@ export class SecretsManagerAdjustSubscriptionComponent implements OnInit, OnDest
|
||||
: this.options.additionalServiceAccountPrice / 12;
|
||||
}
|
||||
|
||||
get serviceAccountTotal(): number {
|
||||
get serviceAccountTotalCost(): number {
|
||||
return Math.abs(
|
||||
this.formGroup.value.additionalServiceAccounts * this.options.additionalServiceAccountPrice
|
||||
);
|
||||
}
|
||||
|
||||
get seatTotal(): number {
|
||||
get seatTotalCost(): number {
|
||||
return Math.abs(this.formGroup.value.seatCount * this.options.seatPrice);
|
||||
}
|
||||
|
||||
get maxServiceAccountTotal(): number {
|
||||
return Math.abs(
|
||||
(this.formGroup.value.maxAutoscaleServiceAccounts ?? 0) *
|
||||
this.options.additionalServiceAccountPrice
|
||||
);
|
||||
get maxAdditionalServiceAccounts(): number {
|
||||
const maxTotalServiceAccounts = this.formGroup.value.maxAutoscaleServiceAccounts ?? 0;
|
||||
return Math.max(0, maxTotalServiceAccounts - this.options.baseServiceAccountCount);
|
||||
}
|
||||
|
||||
get maxSeatTotal(): number {
|
||||
get maxServiceAccountTotalCost(): number {
|
||||
return this.maxAdditionalServiceAccounts * this.options.additionalServiceAccountPrice;
|
||||
}
|
||||
|
||||
get maxSeatTotalCost(): number {
|
||||
return Math.abs((this.formGroup.value.maxAutoscaleSeats ?? 0) * this.options.seatPrice);
|
||||
}
|
||||
|
||||
@ -115,7 +117,7 @@ export class SecretsManagerAdjustSubscriptionComponent implements OnInit, OnDest
|
||||
|
||||
if (value.limitServiceAccounts) {
|
||||
maxAutoscaleServiceAccountsControl.setValidators([
|
||||
Validators.min(value.additionalServiceAccounts),
|
||||
Validators.min(value.additionalServiceAccounts + this.options.baseServiceAccountCount),
|
||||
]);
|
||||
maxAutoscaleServiceAccountsControl.enable({ emitEvent: false });
|
||||
} else {
|
||||
|
@ -2,6 +2,8 @@ import { Component, EventEmitter, Input, Output } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import { InternalOrganizationServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { OrganizationData } from "@bitwarden/common/admin-console/models/data/organization.data";
|
||||
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
|
||||
import { SecretsManagerSubscribeRequest } from "@bitwarden/common/billing/models/request/sm-subscribe.request";
|
||||
import { PlanResponse } from "@bitwarden/common/billing/models/response/plan.response";
|
||||
@ -25,7 +27,8 @@ export class SecretsManagerSubscribeStandaloneComponent {
|
||||
private formBuilder: FormBuilder,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService,
|
||||
private organizationApiService: OrganizationApiServiceAbstraction
|
||||
private organizationApiService: OrganizationApiServiceAbstraction,
|
||||
private organizationService: InternalOrganizationServiceAbstraction
|
||||
) {}
|
||||
|
||||
submit = async () => {
|
||||
@ -37,7 +40,15 @@ export class SecretsManagerSubscribeStandaloneComponent {
|
||||
? this.formGroup.value.additionalServiceAccounts
|
||||
: 0;
|
||||
|
||||
await this.organizationApiService.subscribeToSecretsManager(this.organization.id, request);
|
||||
const profileOrganization = await this.organizationApiService.subscribeToSecretsManager(
|
||||
this.organization.id,
|
||||
request
|
||||
);
|
||||
const organizationData = new OrganizationData(profileOrganization, {
|
||||
isMember: this.organization.isMember,
|
||||
isProviderUser: this.organization.isProviderUser,
|
||||
});
|
||||
await this.organizationService.upsert(organizationData);
|
||||
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("subscriptionUpdated"));
|
||||
|
||||
|
@ -56,10 +56,13 @@
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "additionalServiceAccounts" | i18n }}</bit-label>
|
||||
<input bitInput formControlName="additionalServiceAccounts" type="number" />
|
||||
<bit-hint>{{
|
||||
"additionalServiceAccountsDesc"
|
||||
| i18n : serviceAccountsIncluded : (monthlyCostPerServiceAccount | currency : "$")
|
||||
}}</bit-hint>
|
||||
<bit-hint>
|
||||
{{ "includedServiceAccounts" | i18n : serviceAccountsIncluded }}
|
||||
{{
|
||||
"addAdditionalServiceAccounts"
|
||||
| i18n : (monthlyCostPerServiceAccount | currency : "$")
|
||||
}}
|
||||
</bit-hint>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
|
||||
|
@ -7027,15 +7027,20 @@
|
||||
"additionalServiceAccounts": {
|
||||
"message": "Additional service accounts"
|
||||
},
|
||||
"additionalServiceAccountsDesc": {
|
||||
"message": "Your plan comes with $COUNT$ service accounts. You can add additional service accounts for $COST$ per month.",
|
||||
"includedServiceAccounts": {
|
||||
"message": "Your plan comes with $COUNT$ service accounts.",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"content": "$1",
|
||||
"example": "50"
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"addAdditionalServiceAccounts": {
|
||||
"message": "You can add additional service accounts for $COST$ per month.",
|
||||
"placeholders": {
|
||||
"cost": {
|
||||
"content": "$2",
|
||||
"content": "$1",
|
||||
"example": "$0.50"
|
||||
}
|
||||
}
|
||||
@ -7063,5 +7068,24 @@
|
||||
},
|
||||
"maxServiceAccountCost": {
|
||||
"message": "Max potential service account cost"
|
||||
},
|
||||
"smBetaEndedDesc": {
|
||||
"message": "The Secrets Manager Beta ended $BETA_ENDING_DATE$. You have $DAYS$ days left to add Secrets Manager to your paid subscription and maintain access to Secrets Manager data. Contact Customer Success to add Secrets Manager to your subscription.",
|
||||
"placeholders": {
|
||||
"beta_ending_date": {
|
||||
"content": "$1",
|
||||
"example": "August 1, 2023"
|
||||
},
|
||||
"days": {
|
||||
"content": "$2",
|
||||
"example": "11"
|
||||
}
|
||||
}
|
||||
},
|
||||
"betaEnding": {
|
||||
"message": "Beta Ending"
|
||||
},
|
||||
"beta": {
|
||||
"message": "Beta"
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ import { VaultTimeoutService as VaultTimeoutServiceAbstraction } from "@bitwarde
|
||||
import { VaultTimeoutSettingsService as VaultTimeoutSettingsServiceAbstraction } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
|
||||
import {
|
||||
InternalOrganizationService,
|
||||
InternalOrganizationServiceAbstraction,
|
||||
OrganizationService as OrganizationServiceAbstraction,
|
||||
} from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
|
||||
@ -583,7 +583,7 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction";
|
||||
deps: [StateServiceAbstraction],
|
||||
},
|
||||
{
|
||||
provide: InternalOrganizationService,
|
||||
provide: InternalOrganizationServiceAbstraction,
|
||||
useExisting: OrganizationServiceAbstraction,
|
||||
},
|
||||
{
|
||||
|
@ -210,7 +210,7 @@ export abstract class OrganizationUserService {
|
||||
abstract putOrganizationUserBulkEnableSecretsManager(
|
||||
organizationId: string,
|
||||
ids: string[]
|
||||
): Promise<ListResponse<OrganizationUserBulkResponse>>;
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* Delete an organization user
|
||||
|
@ -26,6 +26,7 @@ import { OrganizationApiKeyInformationResponse } from "../../models/response/org
|
||||
import { OrganizationAutoEnrollStatusResponse } from "../../models/response/organization-auto-enroll-status.response";
|
||||
import { OrganizationKeysResponse } from "../../models/response/organization-keys.response";
|
||||
import { OrganizationResponse } from "../../models/response/organization.response";
|
||||
import { ProfileOrganizationResponse } from "../../models/response/profile-organization.response";
|
||||
|
||||
export class OrganizationApiServiceAbstraction {
|
||||
get: (id: string) => Promise<OrganizationResponse>;
|
||||
@ -68,5 +69,8 @@ export class OrganizationApiServiceAbstraction {
|
||||
getSso: (id: string) => Promise<OrganizationSsoResponse>;
|
||||
updateSso: (id: string, request: OrganizationSsoRequest) => Promise<OrganizationSsoResponse>;
|
||||
selfHostedSyncLicense: (id: string) => Promise<void>;
|
||||
subscribeToSecretsManager: (id: string, request: SecretsManagerSubscribeRequest) => Promise<void>;
|
||||
subscribeToSecretsManager: (
|
||||
id: string,
|
||||
request: SecretsManagerSubscribeRequest
|
||||
) => Promise<ProfileOrganizationResponse>;
|
||||
}
|
||||
|
@ -86,6 +86,7 @@ export abstract class OrganizationService {
|
||||
hasOrganizations: () => boolean;
|
||||
}
|
||||
|
||||
export abstract class InternalOrganizationService extends OrganizationService {
|
||||
export abstract class InternalOrganizationServiceAbstraction extends OrganizationService {
|
||||
replace: (organizations: { [id: string]: OrganizationData }) => Promise<void>;
|
||||
upsert: (OrganizationData: OrganizationData | OrganizationData[]) => Promise<void>;
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ import { OrganizationApiKeyInformationResponse } from "../../models/response/org
|
||||
import { OrganizationAutoEnrollStatusResponse } from "../../models/response/organization-auto-enroll-status.response";
|
||||
import { OrganizationKeysResponse } from "../../models/response/organization-keys.response";
|
||||
import { OrganizationResponse } from "../../models/response/organization.response";
|
||||
import { ProfileOrganizationResponse } from "../../models/response/profile-organization.response";
|
||||
|
||||
export class OrganizationApiService implements OrganizationApiServiceAbstraction {
|
||||
constructor(private apiService: ApiService, private syncService: SyncService) {}
|
||||
@ -311,13 +312,14 @@ export class OrganizationApiService implements OrganizationApiServiceAbstraction
|
||||
async subscribeToSecretsManager(
|
||||
id: string,
|
||||
request: SecretsManagerSubscribeRequest
|
||||
): Promise<void> {
|
||||
return await this.apiService.send(
|
||||
): Promise<ProfileOrganizationResponse> {
|
||||
const r = await this.apiService.send(
|
||||
"POST",
|
||||
"/organizations/" + id + "/subscribe-secrets-manager",
|
||||
request,
|
||||
true,
|
||||
false
|
||||
true
|
||||
);
|
||||
return new ProfileOrganizationResponse(r);
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import { BehaviorSubject, concatMap, map, Observable } from "rxjs";
|
||||
|
||||
import { StateService } from "../../../platform/abstractions/state.service";
|
||||
import {
|
||||
InternalOrganizationService as InternalOrganizationServiceAbstraction,
|
||||
InternalOrganizationServiceAbstraction,
|
||||
isMember,
|
||||
} from "../../abstractions/organization/organization.service.abstraction";
|
||||
import { OrganizationData } from "../../models/data/organization.data";
|
||||
|
@ -12,6 +12,7 @@ export class OrganizationSubscriptionResponse extends OrganizationResponse {
|
||||
upcomingInvoice: BillingSubscriptionUpcomingInvoiceResponse;
|
||||
expiration: string;
|
||||
expirationWithoutGracePeriod: string;
|
||||
secretsManagerBeta: boolean;
|
||||
|
||||
constructor(response: any) {
|
||||
super(response);
|
||||
@ -26,5 +27,6 @@ export class OrganizationSubscriptionResponse extends OrganizationResponse {
|
||||
: new BillingSubscriptionUpcomingInvoiceResponse(upcomingInvoice);
|
||||
this.expiration = this.getResponseProperty("Expiration");
|
||||
this.expirationWithoutGracePeriod = this.getResponseProperty("ExpirationWithoutGracePeriod");
|
||||
this.secretsManagerBeta = this.getResponseProperty("SecretsManagerBeta");
|
||||
}
|
||||
}
|
||||
|
@ -358,4 +358,32 @@ describe("Utils Service", () => {
|
||||
expect(actual.protocol).toBe("http:");
|
||||
});
|
||||
});
|
||||
|
||||
describe("daysRemaining", () => {
|
||||
beforeAll(() => {
|
||||
const now = new Date(2023, 9, 2, 10);
|
||||
jest.spyOn(Date, "now").mockReturnValue(now.getTime());
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it("should return 0 for equal dates", () => {
|
||||
expect(Utils.daysRemaining(new Date(2023, 9, 2))).toBe(0);
|
||||
expect(Utils.daysRemaining(new Date(2023, 9, 2, 12))).toBe(0);
|
||||
});
|
||||
|
||||
it("should return 0 for dates in the past", () => {
|
||||
expect(Utils.daysRemaining(new Date(2020, 5, 11))).toBe(0);
|
||||
expect(Utils.daysRemaining(new Date(2023, 9, 1))).toBe(0);
|
||||
});
|
||||
|
||||
it("should handle future dates", () => {
|
||||
expect(Utils.daysRemaining(new Date(2023, 9, 3, 10))).toBe(1);
|
||||
expect(Utils.daysRemaining(new Date(2023, 10, 12, 10))).toBe(41);
|
||||
// leap year
|
||||
expect(Utils.daysRemaining(new Date(2024, 9, 2, 10))).toBe(366);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -538,6 +538,16 @@ export class Utils {
|
||||
return of(undefined).pipe(switchMap(() => generator()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of days remaining before a target date arrives.
|
||||
* Returns 0 if the day has already passed.
|
||||
*/
|
||||
static daysRemaining(targetDate: Date): number {
|
||||
const diffTime = targetDate.getTime() - Date.now();
|
||||
const msPerDay = 86400000;
|
||||
return Math.max(0, Math.floor(diffTime / msPerDay));
|
||||
}
|
||||
|
||||
private static isAppleMobile(win: Window) {
|
||||
return (
|
||||
win.navigator.userAgent.match(/iPhone/i) != null ||
|
||||
|
@ -209,15 +209,14 @@ export class OrganizationUserServiceImplementation implements OrganizationUserSe
|
||||
async putOrganizationUserBulkEnableSecretsManager(
|
||||
organizationId: string,
|
||||
ids: string[]
|
||||
): Promise<ListResponse<OrganizationUserBulkResponse>> {
|
||||
const r = await this.apiService.send(
|
||||
): Promise<void> {
|
||||
await this.apiService.send(
|
||||
"PUT",
|
||||
"/organizations/" + organizationId + "/users/enable-secrets-manager",
|
||||
new OrganizationUserBulkRequest(ids),
|
||||
true,
|
||||
true
|
||||
false
|
||||
);
|
||||
return new ListResponse(r, OrganizationUserBulkResponse);
|
||||
}
|
||||
|
||||
putOrganizationUser(
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ApiService } from "../../../abstractions/api.service";
|
||||
import { SettingsService } from "../../../abstractions/settings.service";
|
||||
import { InternalOrganizationService } from "../../../admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { InternalOrganizationServiceAbstraction } from "../../../admin-console/abstractions/organization/organization.service.abstraction";
|
||||
import { InternalPolicyService } from "../../../admin-console/abstractions/policy/policy.service.abstraction";
|
||||
import { ProviderService } from "../../../admin-console/abstractions/provider.service";
|
||||
import { OrganizationData } from "../../../admin-console/models/data/organization.data";
|
||||
@ -55,7 +55,7 @@ export class SyncService implements SyncServiceAbstraction {
|
||||
private stateService: StateService,
|
||||
private providerService: ProviderService,
|
||||
private folderApiService: FolderApiServiceAbstraction,
|
||||
private organizationService: InternalOrganizationService,
|
||||
private organizationService: InternalOrganizationServiceAbstraction,
|
||||
private sendApiService: SendApiService,
|
||||
private logoutCallback: (expired: boolean) => Promise<void>
|
||||
) {}
|
||||
|
Loading…
Reference in New Issue
Block a user