1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-23 19:53:43 +00:00

[AC-1081] Merge feature/billing-obfuscation (#5172)

* [AC-431] Add new organization invite process (#4841)

* [AC-431] Added properties 'key' and 'keys' to OrganizationUserAcceptRequest

* [AC-431] On organization accept added check for 'initOrganization' flag and send encrypt keys if true

* [AC-431] Reverted changes on AcceptOrganizationComponent and OrganizationUserAcceptRequest

* [AC-431] Created OrganizationUserAcceptInitRequest

* [AC-431] Added method postOrganizationUserAcceptInit to OrganizationUserService

* [AC-431] Created AcceptInitOrganizationComponent and added routing config. Added 'inviteInitAcceptedDesc' to messages

* [AC-431] Remove blank line

* [AC-431] Remove requirement for logging in again

* [AC-431] Removed accept-init-organization.component.html

* Update libs/common/src/abstractions/organization-user/organization-user.service.ts

Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>

* [AC-431] Sending collection name when initializing an org

* [AC-431] Deleted component accept-init-organization and incorporated logic into accept-organization

* Update libs/common/src/abstractions/organization-user/organization-user.service.ts

Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>

* [AC-431] Returning promise chains

* [AC-431] Moved ReAuth check to org accept only

* [AC-431] Fixed import issues

---------

Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>

* [AC-434] Hide billing screen for reseller clients (#4955)

* [AC-434] Retrieving ProviderType for each Org

* [AC-434] Hide subscription details if user cannot manage billing

* [AC-434] Renamed providerType to provider-type

* [AC-434] Reverted change that showed Billing History and Payment Methods tabs

* [AC-434] Hiding Secrets Manager enroll

* [AC-434] Renamed Billing access variables to be more readable

* Apply suggestions from code review

Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>

* [AC-434] Reduce duplication in permission code

* [AC-434] npm prettier

* [AC-434] Changed selfhost subscription permission

* [AC-434] Added canEditSubscription check for change plan buttons

* [AC-434] Removed message displaying provider name in subscription

* [AC-434] canEditSubscription logic depends on canViewSubscription

* [AC-434] Hiding next charge value for users without billing edit permission

* [AC-434] Changed canViewSubscription and canEditSubscription to be clearer

* [AC-434] Altered BillingSubscriptionItemResponse.amount and BillingSubscriptionUpcomingInvoiceResponse.amount to nullable

* [AC-434] Reverted change on BillingSubscriptionItemResponse.amount

---------

Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>

* Updated IsPaidOrgGuard reference from org.CanManageBilling to canEditSubscription

---------

Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
This commit is contained in:
Rui Tomé
2023-04-14 11:14:18 +01:00
committed by GitHub
parent b3d4d9898e
commit e3f31ac741
24 changed files with 280 additions and 152 deletions

View File

@@ -27,7 +27,7 @@ export class IsPaidOrgGuard implements CanActivate {
if (org.isFreeOrg) {
// Users without billing permission can't access billing
if (!org.canManageBilling) {
if (!org.canEditSubscription) {
await this.platformUtilsService.showDialog(
this.i18nService.t("notAvailableForFreeOrganization"),
this.i18nService.t("upgradeOrganization"),

View File

@@ -151,7 +151,7 @@ export class CollectionsComponent implements OnInit {
const orgUpgradeSimpleDialogOpts: SimpleDialogOptions = {
title: this.i18nService.t("upgradeOrganization"),
content: this.i18nService.t(
this.organization.canManageBilling
this.organization.canEditSubscription
? "freeOrgMaxCollectionReachedManageBilling"
: "freeOrgMaxCollectionReachedNoManageBilling",
this.organization.maxCollections
@@ -159,7 +159,7 @@ export class CollectionsComponent implements OnInit {
type: SimpleDialogType.PRIMARY,
};
if (this.organization.canManageBilling) {
if (this.organization.canEditSubscription) {
orgUpgradeSimpleDialogOpts.acceptButtonText = this.i18nService.t("upgrade");
} else {
orgUpgradeSimpleDialogOpts.acceptButtonText = this.i18nService.t("ok");
@@ -173,7 +173,7 @@ export class CollectionsComponent implements OnInit {
return;
}
if (result == SimpleDialogCloseType.ACCEPT && this.organization.canManageBilling) {
if (result == SimpleDialogCloseType.ACCEPT && this.organization.canEditSubscription) {
this.router.navigate(
["/organizations", this.organization.id, "billing", "subscription"],
{ queryParams: { upgrade: true } }

View File

@@ -347,7 +347,7 @@ export class PeopleComponent
const orgUpgradeSimpleDialogOpts: SimpleDialogOptions = {
title: this.i18nService.t("upgradeOrganization"),
content: this.i18nService.t(
this.organization.canManageBilling
this.organization.canEditSubscription
? "freeOrgInvLimitReachedManageBilling"
: "freeOrgInvLimitReachedNoManageBilling",
this.organization.seats
@@ -355,7 +355,7 @@ export class PeopleComponent
type: SimpleDialogType.PRIMARY,
};
if (this.organization.canManageBilling) {
if (this.organization.canEditSubscription) {
orgUpgradeSimpleDialogOpts.acceptButtonText = this.i18nService.t("upgrade");
} else {
orgUpgradeSimpleDialogOpts.acceptButtonText = this.i18nService.t("ok");
@@ -369,7 +369,7 @@ export class PeopleComponent
return;
}
if (result == SimpleDialogCloseType.ACCEPT && this.organization.canManageBilling) {
if (result == SimpleDialogCloseType.ACCEPT && this.organization.canEditSubscription) {
this.router.navigate(["/organizations", this.organization.id, "billing", "subscription"], {
queryParams: { upgrade: true },
});

View File

@@ -37,7 +37,7 @@
type="text"
name="BillingEmail"
[(ngModel)]="org.billingEmail"
[disabled]="selfHosted || !canManageBilling"
[disabled]="selfHosted || !canEditSubscription"
/>
</div>
<div class="form-group">
@@ -48,7 +48,7 @@
type="text"
name="BusinessName"
[(ngModel)]="org.businessName"
[disabled]="selfHosted || !canManageBilling"
[disabled]="selfHosted || !canEditSubscription"
/>
</div>
</div>

View File

@@ -33,7 +33,7 @@ export class AccountComponent {
rotateApiKeyModalRef: ViewContainerRef;
selfHosted = false;
canManageBilling = true;
canEditSubscription = true;
loading = true;
canUseApi = false;
org: OrganizationResponse;
@@ -60,7 +60,9 @@ export class AccountComponent {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.route.parent.parent.params.subscribe(async (params) => {
this.organizationId = params.organizationId;
this.canManageBilling = this.organizationService.get(this.organizationId).canManageBilling;
this.canEditSubscription = this.organizationService.get(
this.organizationId
).canEditSubscription;
try {
this.org = await this.organizationApiService.get(this.organizationId);
this.canUseApi = this.org.useApi;

View File

@@ -34,7 +34,7 @@ const routes: Routes = [
canActivate: [OrganizationPermissionsGuard],
data: {
titleId: "paymentMethod",
organizationPermissions: (org: Organization) => org.canManageBilling,
organizationPermissions: (org: Organization) => org.canEditPaymentMethods,
},
},
{
@@ -43,7 +43,7 @@ const routes: Routes = [
canActivate: [OrganizationPermissionsGuard],
data: {
titleId: "billingHistory",
organizationPermissions: (org: Organization) => org.canManageBilling,
organizationPermissions: (org: Organization) => org.canViewBillingHistory,
},
},
],

View File

@@ -21,7 +21,12 @@ export class OrganizationBillingTabComponent implements OnInit {
ngOnInit() {
this.showPaymentAndHistory$ = this.route.params.pipe(
switchMap((params) => this.organizationService.get$(params.organizationId)),
map((org) => !this.platformUtilsService.isSelfHost() && org.canManageBilling)
map(
(org) =>
!this.platformUtilsService.isSelfHost() &&
org.canViewBillingHistory &&
org.canEditPaymentMethods
)
);
}
}

View File

@@ -17,7 +17,7 @@
</ng-container>
<app-org-subscription-hidden
*ngIf="firstLoaded && !userOrg.canManageBilling"
*ngIf="firstLoaded && !userOrg.canViewSubscription"
[providerName]="userOrg.providerName"
></app-org-subscription-hidden>
@@ -64,30 +64,24 @@
</ng-container>
</dl>
</div>
<div class="col-8" *ngIf="subscription">
<strong class="d-block mb-1">{{ "details" | i18n }}</strong>
<table class="table">
<tbody>
<tr *ngFor="let i of subscription.items">
<td>
{{ i.name }} {{ i.quantity > 1 ? "&times;" + i.quantity : "" }} @
{{ i.amount | currency : "$" }}
</td>
<td>{{ i.quantity * i.amount | currency : "$" }} /{{ i.interval | i18n }}</td>
</tr>
</tbody>
</table>
</div>
<ng-container *ngIf="userOrg?.providerId != null">
<div class="col-sm">
<dl>
<dt>{{ "provider" | i18n }}</dt>
<dd>{{ "yourProviderIs" | i18n : userOrg.providerName }}</dd>
</dl>
<ng-container *ngIf="userOrg.canEditSubscription">
<div class="col-8" *ngIf="subscription">
<strong class="d-block mb-1">{{ "details" | i18n }}</strong>
<table class="table">
<tbody>
<tr *ngFor="let i of subscription.items">
<td>
{{ i.name }} {{ i.quantity > 1 ? "&times;" + i.quantity : "" }} @
{{ i.amount | currency : "$" }}
</td>
<td>{{ i.quantity * i.amount | currency : "$" }} /{{ i.interval | i18n }}</td>
</tr>
</tbody>
</table>
</div>
</ng-container>
</div>
<ng-container>
<ng-container *ngIf="userOrg.canEditSubscription">
<button
bitButton
buttonType="secondary"
@@ -105,80 +99,84 @@
></app-change-plan>
</ng-container>
<sm-enroll
*ngIf="isAdmin"
[enabled]="sub?.useSecretsManager"
[organizationId]="organizationId"
></sm-enroll>
<h2 class="spaced-header">{{ "manageSubscription" | i18n }}</h2>
<p class="mb-4">{{ subscriptionDesc }}</p>
<ng-container
*ngIf="
subscription && canAdjustSeats && !subscription.cancelled && !subscriptionMarkedForCancel
"
>
<div class="mt-3">
<app-adjust-subscription
[seatPrice]="seatPrice"
[organizationId]="organizationId"
[interval]="billingInterval"
[currentSeatCount]="seats"
[maxAutoscaleSeats]="maxAutoscaleSeats"
(onAdjusted)="subscriptionAdjusted()"
>
</app-adjust-subscription>
</div>
<ng-container *ngIf="userOrg.canEditSubscription">
<sm-enroll
*ngIf="isAdmin"
[enabled]="sub?.useSecretsManager"
[organizationId]="organizationId"
></sm-enroll>
</ng-container>
<button
bitButton
buttonType="danger"
type="button"
[bitAction]="removeSponsorship"
*ngIf="isSponsoredSubscription"
>
{{ "removeSponsorship" | i18n }}
</button>
<h2 class="spaced-header">{{ "storage" | i18n }}</h2>
<p>{{ "subscriptionStorage" | i18n : sub.maxStorageGb || 0 : sub.storageName || "0 MB" }}</p>
<div class="progress">
<div
class="progress-bar bg-success"
role="progressbar"
[ngStyle]="{ width: storageProgressWidth + '%' }"
[attr.aria-valuenow]="storagePercentage"
aria-valuemin="0"
aria-valuemax="100"
<ng-container *ngIf="userOrg.canEditSubscription">
<h2 class="spaced-header">{{ "manageSubscription" | i18n }}</h2>
<p class="mb-4">{{ subscriptionDesc }}</p>
<ng-container
*ngIf="
subscription && canAdjustSeats && !subscription.cancelled && !subscriptionMarkedForCancel
"
>
{{ storagePercentage / 100 | percent }}
</div>
</div>
<ng-container *ngIf="subscription && !subscription.cancelled && !subscriptionMarkedForCancel">
<div class="mt-3">
<div class="d-flex" *ngIf="!showAdjustStorage">
<button bitButton buttonType="secondary" type="button" (click)="adjustStorage(true)">
{{ "addStorage" | i18n }}
</button>
<button
bitButton
buttonType="secondary"
type="button"
class="ml-1"
(click)="adjustStorage(false)"
<div class="mt-3">
<app-adjust-subscription
[seatPrice]="seatPrice"
[organizationId]="organizationId"
[interval]="billingInterval"
[currentSeatCount]="seats"
[maxAutoscaleSeats]="maxAutoscaleSeats"
(onAdjusted)="subscriptionAdjusted()"
>
{{ "removeStorage" | i18n }}
</button>
</app-adjust-subscription>
</div>
</ng-container>
<button
bitButton
buttonType="danger"
type="button"
[bitAction]="removeSponsorship"
*ngIf="isSponsoredSubscription"
>
{{ "removeSponsorship" | i18n }}
</button>
<h2 class="spaced-header">{{ "storage" | i18n }}</h2>
<p>{{ "subscriptionStorage" | i18n : sub.maxStorageGb || 0 : sub.storageName || "0 MB" }}</p>
<div class="progress">
<div
class="progress-bar bg-success"
role="progressbar"
[ngStyle]="{ width: storageProgressWidth + '%' }"
[attr.aria-valuenow]="storagePercentage"
aria-valuemin="0"
aria-valuemax="100"
>
{{ storagePercentage / 100 | percent }}
</div>
<app-adjust-storage
[storageGbPrice]="storageGbPrice"
[add]="adjustStorageAdd"
[organizationId]="organizationId"
[interval]="billingInterval"
(onAdjusted)="closeStorage(true)"
(onCanceled)="closeStorage(false)"
*ngIf="showAdjustStorage"
></app-adjust-storage>
</div>
<ng-container *ngIf="subscription && !subscription.cancelled && !subscriptionMarkedForCancel">
<div class="mt-3">
<div class="d-flex" *ngIf="!showAdjustStorage">
<button bitButton buttonType="secondary" type="button" (click)="adjustStorage(true)">
{{ "addStorage" | i18n }}
</button>
<button
bitButton
buttonType="secondary"
type="button"
class="ml-1"
(click)="adjustStorage(false)"
>
{{ "removeStorage" | i18n }}
</button>
</div>
<app-adjust-storage
[storageGbPrice]="storageGbPrice"
[add]="adjustStorageAdd"
[organizationId]="organizationId"
[interval]="billingInterval"
(onAdjusted)="closeStorage(true)"
(onCanceled)="closeStorage(false)"
*ngIf="showAdjustStorage"
></app-adjust-storage>
</div>
</ng-container>
</ng-container>
<h2 class="spaced-header">{{ "selfHostingTitle" | i18n }}</h2>
@@ -214,20 +212,22 @@
(onCanceled)="closeDownloadLicense()"
></app-download-license>
</div>
<h2 class="spaced-header">{{ "additionalOptions" | i18n }}</h2>
<p class="mb-4">
{{ "additionalOptionsDesc" | i18n }}
</p>
<div class="d-flex">
<button
bitButton
buttonType="danger"
[bitAction]="cancel"
type="button"
class="ml-1"
*ngIf="subscription && !subscription.cancelled && !subscriptionMarkedForCancel"
>
{{ "cancelSubscription" | i18n }}
</button>
</div>
<ng-container *ngIf="userOrg.canEditSubscription">
<h2 class="spaced-header">{{ "additionalOptions" | i18n }}</h2>
<p class="mb-4">
{{ "additionalOptionsDesc" | i18n }}
</p>
<div class="d-flex">
<button
bitButton
buttonType="danger"
[bitAction]="cancel"
type="button"
class="ml-1"
*ngIf="subscription && !subscription.cancelled && !subscriptionMarkedForCancel"
>
{{ "cancelSubscription" | i18n }}
</button>
</div>
</ng-container>
</ng-container>

View File

@@ -77,7 +77,7 @@ export class OrganizationSubscriptionCloudComponent implements OnInit, OnDestroy
}
this.loading = true;
this.userOrg = this.organizationService.get(this.organizationId);
if (this.userOrg.canManageBilling) {
if (this.userOrg.canViewSubscription) {
this.sub = await this.organizationApiService.getSubscription(this.organizationId);
}

View File

@@ -18,7 +18,7 @@
</ng-container>
<app-org-subscription-hidden
*ngIf="firstLoaded && !userOrg.canManageBilling"
*ngIf="firstLoaded && !userOrg.canViewSubscription"
[providerName]="userOrg.providerName"
></app-org-subscription-hidden>

View File

@@ -101,7 +101,7 @@ export class OrganizationSubscriptionSelfhostComponent implements OnInit, OnDest
}
this.loading = true;
this.userOrg = this.organizationService.get(this.organizationId);
if (this.userOrg.canManageBilling) {
if (this.userOrg.canViewSubscription) {
this.sub = await this.organizationApiService.getSubscription(this.organizationId);
}

View File

@@ -111,7 +111,7 @@ export class VaultHeaderComponent {
const orgUpgradeSimpleDialogOpts: SimpleDialogOptions = {
title: this.i18nService.t("upgradeOrganization"),
content: this.i18nService.t(
this.organization.canManageBilling
this.organization.canEditSubscription
? "freeOrgMaxCollectionReachedManageBilling"
: "freeOrgMaxCollectionReachedNoManageBilling",
this.organization.maxCollections
@@ -119,7 +119,7 @@ export class VaultHeaderComponent {
type: SimpleDialogType.PRIMARY,
};
if (this.organization.canManageBilling) {
if (this.organization.canEditSubscription) {
orgUpgradeSimpleDialogOpts.acceptButtonText = this.i18nService.t("upgrade");
} else {
orgUpgradeSimpleDialogOpts.acceptButtonText = this.i18nService.t("ok");
@@ -133,7 +133,7 @@ export class VaultHeaderComponent {
return;
}
if (result == SimpleDialogCloseType.ACCEPT && this.organization.canManageBilling) {
if (result == SimpleDialogCloseType.ACCEPT && this.organization.canEditSubscription) {
this.router.navigate(["/organizations", this.organization.id, "billing", "subscription"], {
queryParams: { upgrade: true },
});