From e3f31ac741049b27ae8401d31f1b9589b82f5cee Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rui=20Tom=C3=A9?=
<108268980+r-tome@users.noreply.github.com>
Date: Fri, 14 Apr 2023 11:14:18 +0100
Subject: [PATCH] [AC-1081] Merge feature/billing-obfuscation (#5172)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* [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>
---
.../organizations/guards/is-paid-org.guard.ts | 2 +-
.../manage/collections.component.ts | 6 +-
.../organizations/members/people.component.ts | 6 +-
.../settings/account.component.html | 4 +-
.../settings/account.component.ts | 6 +-
.../organization-billing-routing.module.ts | 4 +-
.../organization-billing-tab.component.ts | 7 +-
...nization-subscription-cloud.component.html | 214 +++++++++---------
...ganization-subscription-cloud.component.ts | 2 +-
...ation-subscription-selfhost.component.html | 2 +-
...ization-subscription-selfhost.component.ts | 2 +-
.../vault-header/vault-header.component.ts | 6 +-
.../src/auth/accept-organization.component.ts | 88 +++++--
apps/web/src/locales/en/messages.json | 3 +
.../organization-user.service.ts | 15 ++
.../organization-user/requests/index.ts | 1 +
.../organization-user-accept-init.request.ts | 8 +
.../models/data/organization.data.ts | 4 +-
.../models/domain/organization.ts | 26 ++-
.../response/profile-organization.response.ts | 4 +-
.../models/response/subscription.response.ts | 2 +-
libs/common/src/enums/index.ts | 1 +
libs/common/src/enums/provider-type.enum.ts | 4 +
...rganization-user.service.implementation.ts | 15 ++
24 files changed, 280 insertions(+), 152 deletions(-)
create mode 100644 libs/common/src/abstractions/organization-user/requests/organization-user-accept-init.request.ts
create mode 100644 libs/common/src/enums/provider-type.enum.ts
diff --git a/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.ts b/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.ts
index a74e152f88..79ad01eea9 100644
--- a/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.ts
+++ b/apps/web/src/app/admin-console/organizations/guards/is-paid-org.guard.ts
@@ -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"),
diff --git a/apps/web/src/app/admin-console/organizations/manage/collections.component.ts b/apps/web/src/app/admin-console/organizations/manage/collections.component.ts
index 5c18c1e316..8026ee4a37 100644
--- a/apps/web/src/app/admin-console/organizations/manage/collections.component.ts
+++ b/apps/web/src/app/admin-console/organizations/manage/collections.component.ts
@@ -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 } }
diff --git a/apps/web/src/app/admin-console/organizations/members/people.component.ts b/apps/web/src/app/admin-console/organizations/members/people.component.ts
index 1e1ea6f94d..cc5cc4db3a 100644
--- a/apps/web/src/app/admin-console/organizations/members/people.component.ts
+++ b/apps/web/src/app/admin-console/organizations/members/people.component.ts
@@ -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 },
});
diff --git a/apps/web/src/app/admin-console/organizations/settings/account.component.html b/apps/web/src/app/admin-console/organizations/settings/account.component.html
index 1d11412bfb..096ae89015 100644
--- a/apps/web/src/app/admin-console/organizations/settings/account.component.html
+++ b/apps/web/src/app/admin-console/organizations/settings/account.component.html
@@ -37,7 +37,7 @@
type="text"
name="BillingEmail"
[(ngModel)]="org.billingEmail"
- [disabled]="selfHosted || !canManageBilling"
+ [disabled]="selfHosted || !canEditSubscription"
/>
@@ -48,7 +48,7 @@
type="text"
name="BusinessName"
[(ngModel)]="org.businessName"
- [disabled]="selfHosted || !canManageBilling"
+ [disabled]="selfHosted || !canEditSubscription"
/>
diff --git a/apps/web/src/app/admin-console/organizations/settings/account.component.ts b/apps/web/src/app/admin-console/organizations/settings/account.component.ts
index 75dae9015a..0072e1f3ae 100644
--- a/apps/web/src/app/admin-console/organizations/settings/account.component.ts
+++ b/apps/web/src/app/admin-console/organizations/settings/account.component.ts
@@ -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;
diff --git a/apps/web/src/app/billing/organizations/organization-billing-routing.module.ts b/apps/web/src/app/billing/organizations/organization-billing-routing.module.ts
index 5ba8ab4415..eae0fdfdec 100644
--- a/apps/web/src/app/billing/organizations/organization-billing-routing.module.ts
+++ b/apps/web/src/app/billing/organizations/organization-billing-routing.module.ts
@@ -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,
},
},
],
diff --git a/apps/web/src/app/billing/organizations/organization-billing-tab.component.ts b/apps/web/src/app/billing/organizations/organization-billing-tab.component.ts
index 8acf90254e..11609dc5e7 100644
--- a/apps/web/src/app/billing/organizations/organization-billing-tab.component.ts
+++ b/apps/web/src/app/billing/organizations/organization-billing-tab.component.ts
@@ -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
+ )
);
}
}
diff --git a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html
index c92b09c7e3..c28bdd4ddc 100644
--- a/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html
+++ b/apps/web/src/app/billing/organizations/organization-subscription-cloud.component.html
@@ -17,7 +17,7 @@
@@ -64,30 +64,24 @@
-
-
{{ "details" | i18n }}
-
-
-
-
- {{ i.name }} {{ i.quantity > 1 ? "×" + i.quantity : "" }} @
- {{ i.amount | currency : "$" }}
- |
- {{ i.quantity * i.amount | currency : "$" }} /{{ i.interval | i18n }} |
-
-
-
-
-
-
-
- - {{ "provider" | i18n }}
- - {{ "yourProviderIs" | i18n : userOrg.providerName }}
-
+
+
+
{{ "details" | i18n }}
+
+
+
+
+ {{ i.name }} {{ i.quantity > 1 ? "×" + i.quantity : "" }} @
+ {{ i.amount | currency : "$" }}
+ |
+ {{ i.quantity * i.amount | currency : "$" }} /{{ i.interval | i18n }} |
+
+
+
-
+
-
-
-
- {{ subscriptionDesc }}
-
-
+
+
-
-
- {{ "subscriptionStorage" | i18n : sub.maxStorageGb || 0 : sub.storageName || "0 MB" }}
-
-
+
+
{{ subscriptionDesc }}
+
- {{ storagePercentage / 100 | percent }}
-
-
-
-
-
-
-
+
+
+
+
+
+
{{ "subscriptionStorage" | i18n : sub.maxStorageGb || 0 : sub.storageName || "0 MB" }}
+
+
+ {{ storagePercentage / 100 | percent }}
-
+
+
+
+
+
+
+
+
+
@@ -214,20 +212,22 @@
(onCanceled)="closeDownloadLicense()"
>
-
-
- {{ "additionalOptionsDesc" | i18n }}
-
-
-
-
+
+
+
+ {{ "additionalOptionsDesc" | 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 9cc8d4cfc4..3ee9d99b2a 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
@@ -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);
}
diff --git a/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.html b/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.html
index a7ba79a79d..08fb12e76e 100644
--- a/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.html
+++ b/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.html
@@ -18,7 +18,7 @@
diff --git a/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.ts b/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.ts
index 1956e7361f..a6afb20aad 100644
--- a/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.ts
+++ b/apps/web/src/app/billing/organizations/organization-subscription-selfhost.component.ts
@@ -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);
}
diff --git a/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.ts b/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.ts
index 95349c85d1..385b198638 100644
--- a/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.ts
+++ b/apps/web/src/app/vault/org-vault/vault-header/vault-header.component.ts
@@ -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 },
});
diff --git a/apps/web/src/auth/accept-organization.component.ts b/apps/web/src/auth/accept-organization.component.ts
index 561c7567a0..64f1f8a22f 100644
--- a/apps/web/src/auth/accept-organization.component.ts
+++ b/apps/web/src/auth/accept-organization.component.ts
@@ -6,13 +6,17 @@ import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service";
-import { OrganizationUserAcceptRequest } from "@bitwarden/common/abstractions/organization-user/requests";
+import {
+ OrganizationUserAcceptInitRequest,
+ OrganizationUserAcceptRequest,
+} from "@bitwarden/common/abstractions/organization-user/requests";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
+import { OrganizationKeysRequest } from "@bitwarden/common/admin-console/models/request/organization-keys.request";
import { Utils } from "@bitwarden/common/misc/utils";
import { BaseAcceptComponent } from "../app/common/base.accept.component";
@@ -44,32 +48,33 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent {
}
async authedHandler(qParams: Params): Promise {
- const needsReAuth = (await this.stateService.getOrganizationInvitation()) != null;
- if (!needsReAuth) {
- // Accepting an org invite requires authentication from a logged out state
- this.messagingService.send("logout", { redirect: false });
- await this.prepareOrganizationInvitation(qParams);
- return;
+ const initOrganization =
+ qParams.initOrganization != null && qParams.initOrganization.toLocaleLowerCase() === "true";
+ if (initOrganization) {
+ this.actionPromise = this.acceptInitOrganizationFlow(qParams);
+ } else {
+ const needsReAuth = (await this.stateService.getOrganizationInvitation()) == null;
+ if (needsReAuth) {
+ // Accepting an org invite requires authentication from a logged out state
+ this.messagingService.send("logout", { redirect: false });
+ await this.prepareOrganizationInvitation(qParams);
+ return;
+ }
+
+ // User has already logged in and passed the Master Password policy check
+ this.actionPromise = this.acceptFlow(qParams);
}
- // User has already logged in and passed the Master Password policy check
- this.actionPromise = this.prepareAcceptRequest(qParams).then(async (request) => {
- await this.organizationUserService.postOrganizationUserAccept(
- qParams.organizationId,
- qParams.organizationUserId,
- request
- );
- });
-
- await this.stateService.setOrganizationInvitation(null);
await this.actionPromise;
+ await this.stateService.setOrganizationInvitation(null);
this.platformUtilService.showToast(
"success",
this.i18nService.t("inviteAccepted"),
- this.i18nService.t("inviteAcceptedDesc"),
+ initOrganization
+ ? this.i18nService.t("inviteInitAcceptedDesc")
+ : this.i18nService.t("inviteAcceptedDesc"),
{ timeout: 10000 }
);
-
this.router.navigate(["/vault"]);
}
@@ -77,6 +82,51 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent {
await this.prepareOrganizationInvitation(qParams);
}
+ private async acceptInitOrganizationFlow(qParams: Params): Promise {
+ return this.prepareAcceptInitRequest(qParams).then((request) =>
+ this.organizationUserService.postOrganizationUserAcceptInit(
+ qParams.organizationId,
+ qParams.organizationUserId,
+ request
+ )
+ );
+ }
+
+ private async acceptFlow(qParams: Params): Promise {
+ return this.prepareAcceptRequest(qParams).then((request) =>
+ this.organizationUserService.postOrganizationUserAccept(
+ qParams.organizationId,
+ qParams.organizationUserId,
+ request
+ )
+ );
+ }
+
+ private async prepareAcceptInitRequest(
+ qParams: Params
+ ): Promise {
+ const request = new OrganizationUserAcceptInitRequest();
+ request.token = qParams.token;
+
+ const [encryptedOrgShareKey, orgShareKey] = await this.cryptoService.makeShareKey();
+ const [orgPublicKey, encryptedOrgPrivateKey] = await this.cryptoService.makeKeyPair(
+ orgShareKey
+ );
+ const collection = await this.cryptoService.encrypt(
+ this.i18nService.t("defaultCollection"),
+ orgShareKey
+ );
+
+ request.key = encryptedOrgShareKey.encryptedString;
+ request.keys = new OrganizationKeysRequest(
+ orgPublicKey,
+ encryptedOrgPrivateKey.encryptedString
+ );
+ request.collectionName = collection.encryptedString;
+
+ return request;
+ }
+
private async prepareAcceptRequest(qParams: Params): Promise {
const request = new OrganizationUserAcceptRequest();
request.token = qParams.token;
diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json
index ade4303a36..dd9fbcbe79 100644
--- a/apps/web/src/locales/en/messages.json
+++ b/apps/web/src/locales/en/messages.json
@@ -3126,6 +3126,9 @@
"inviteAcceptedDesc": {
"message": "You can access this organization once an administrator confirms your membership. We'll send you an email when that happens."
},
+ "inviteInitAcceptedDesc": {
+ "message": "You can now access this organization."
+ },
"inviteAcceptFailed": {
"message": "Unable to accept invitation. Ask an organization admin to send a new invitation."
},
diff --git a/libs/common/src/abstractions/organization-user/organization-user.service.ts b/libs/common/src/abstractions/organization-user/organization-user.service.ts
index cfb7f0145c..ef65d34b11 100644
--- a/libs/common/src/abstractions/organization-user/organization-user.service.ts
+++ b/libs/common/src/abstractions/organization-user/organization-user.service.ts
@@ -1,6 +1,7 @@
import { ListResponse } from "../../models/response/list.response";
import {
+ OrganizationUserAcceptInitRequest,
OrganizationUserAcceptRequest,
OrganizationUserBulkConfirmRequest,
OrganizationUserConfirmRequest,
@@ -94,6 +95,20 @@ export abstract class OrganizationUserService {
ids: string[]
): Promise>;
+ /**
+ * Accept an invitation to initialize and join an organization created via the Admin Portal **only**.
+ * This is only used once for the initial Owner, because it also creates the organization's encryption keys.
+ * This should not be used for organizations created via the Web client.
+ * @param organizationId - Identifier for the organization to accept
+ * @param id - Organization user identifier
+ * @param request - Request details for accepting the invitation
+ */
+ abstract postOrganizationUserAcceptInit(
+ organizationId: string,
+ id: string,
+ request: OrganizationUserAcceptInitRequest
+ ): Promise;
+
/**
* Accept an organization user invitation
* @param organizationId - Identifier for the organization to accept
diff --git a/libs/common/src/abstractions/organization-user/requests/index.ts b/libs/common/src/abstractions/organization-user/requests/index.ts
index 8f45e585ee..6c9cec2b4b 100644
--- a/libs/common/src/abstractions/organization-user/requests/index.ts
+++ b/libs/common/src/abstractions/organization-user/requests/index.ts
@@ -1,3 +1,4 @@
+export * from "./organization-user-accept-init.request";
export * from "./organization-user-accept.request";
export * from "./organization-user-bulk-confirm.request";
export * from "./organization-user-confirm.request";
diff --git a/libs/common/src/abstractions/organization-user/requests/organization-user-accept-init.request.ts b/libs/common/src/abstractions/organization-user/requests/organization-user-accept-init.request.ts
new file mode 100644
index 0000000000..d47cb711d2
--- /dev/null
+++ b/libs/common/src/abstractions/organization-user/requests/organization-user-accept-init.request.ts
@@ -0,0 +1,8 @@
+import { OrganizationKeysRequest } from "../../../admin-console/models/request/organization-keys.request";
+
+export class OrganizationUserAcceptInitRequest {
+ token: string;
+ key: string;
+ keys: OrganizationKeysRequest;
+ collectionName: string;
+}
diff --git a/libs/common/src/admin-console/models/data/organization.data.ts b/libs/common/src/admin-console/models/data/organization.data.ts
index 2886445fc5..cfaaff37c7 100644
--- a/libs/common/src/admin-console/models/data/organization.data.ts
+++ b/libs/common/src/admin-console/models/data/organization.data.ts
@@ -1,4 +1,4 @@
-import { ProductType } from "../../../enums";
+import { ProductType, ProviderType } from "../../../enums";
import { OrganizationUserStatusType, OrganizationUserType } from "../../enums";
import { PermissionsApi } from "../api/permissions.api";
import { ProfileOrganizationResponse } from "../response/profile-organization.response";
@@ -36,6 +36,7 @@ export class OrganizationData {
hasPublicAndPrivateKeys: boolean;
providerId: string;
providerName: string;
+ providerType?: ProviderType;
isProviderUser: boolean;
familySponsorshipFriendlyName: string;
familySponsorshipAvailable: boolean;
@@ -80,6 +81,7 @@ export class OrganizationData {
this.hasPublicAndPrivateKeys = response.hasPublicAndPrivateKeys;
this.providerId = response.providerId;
this.providerName = response.providerName;
+ this.providerType = response.providerType;
this.familySponsorshipFriendlyName = response.familySponsorshipFriendlyName;
this.familySponsorshipAvailable = response.familySponsorshipAvailable;
this.planProductType = response.planProductType;
diff --git a/libs/common/src/admin-console/models/domain/organization.ts b/libs/common/src/admin-console/models/domain/organization.ts
index c9c95891a7..b29a9fa7c8 100644
--- a/libs/common/src/admin-console/models/domain/organization.ts
+++ b/libs/common/src/admin-console/models/domain/organization.ts
@@ -1,6 +1,6 @@
import { Jsonify } from "type-fest";
-import { ProductType } from "../../../enums";
+import { ProductType, ProviderType } from "../../../enums";
import { OrganizationUserStatusType, OrganizationUserType } from "../../enums";
import { PermissionsApi } from "../api/permissions.api";
import { OrganizationData } from "../data/organization.data";
@@ -38,6 +38,7 @@ export class Organization {
hasPublicAndPrivateKeys: boolean;
providerId: string;
providerName: string;
+ providerType?: ProviderType;
isProviderUser: boolean;
familySponsorshipFriendlyName: string;
familySponsorshipAvailable: boolean;
@@ -86,6 +87,7 @@ export class Organization {
this.hasPublicAndPrivateKeys = obj.hasPublicAndPrivateKeys;
this.providerId = obj.providerId;
this.providerName = obj.providerName;
+ this.providerType = obj.providerType;
this.isProviderUser = obj.isProviderUser;
this.familySponsorshipFriendlyName = obj.familySponsorshipFriendlyName;
this.familySponsorshipAvailable = obj.familySponsorshipAvailable;
@@ -197,8 +199,26 @@ export class Organization {
return this.canManagePolicies;
}
- get canManageBilling() {
- return this.isOwner && (this.isProviderUser || !this.hasProvider);
+ get canViewSubscription() {
+ if (this.canEditSubscription) {
+ return true;
+ }
+
+ return this.hasProvider && this.providerType === ProviderType.Msp
+ ? this.isProviderUser
+ : this.isOwner;
+ }
+
+ get canEditSubscription() {
+ return this.hasProvider ? this.isProviderUser : this.isOwner;
+ }
+
+ get canEditPaymentMethods() {
+ return this.canEditSubscription;
+ }
+
+ get canViewBillingHistory() {
+ return this.canEditSubscription;
}
get hasProvider() {
diff --git a/libs/common/src/admin-console/models/response/profile-organization.response.ts b/libs/common/src/admin-console/models/response/profile-organization.response.ts
index e94eef0814..18bf4d45e8 100644
--- a/libs/common/src/admin-console/models/response/profile-organization.response.ts
+++ b/libs/common/src/admin-console/models/response/profile-organization.response.ts
@@ -1,4 +1,4 @@
-import { ProductType } from "../../../enums";
+import { ProductType, ProviderType } from "../../../enums";
import { BaseResponse } from "../../../models/response/base.response";
import { OrganizationUserStatusType, OrganizationUserType } from "../../enums";
import { PermissionsApi } from "../api/permissions.api";
@@ -37,6 +37,7 @@ export class ProfileOrganizationResponse extends BaseResponse {
userId: string;
providerId: string;
providerName: string;
+ providerType?: ProviderType;
familySponsorshipFriendlyName: string;
familySponsorshipAvailable: boolean;
planProductType: ProductType;
@@ -82,6 +83,7 @@ export class ProfileOrganizationResponse extends BaseResponse {
this.userId = this.getResponseProperty("UserId");
this.providerId = this.getResponseProperty("ProviderId");
this.providerName = this.getResponseProperty("ProviderName");
+ this.providerType = this.getResponseProperty("ProviderType");
this.familySponsorshipFriendlyName = this.getResponseProperty("FamilySponsorshipFriendlyName");
this.familySponsorshipAvailable = this.getResponseProperty("FamilySponsorshipAvailable");
this.planProductType = this.getResponseProperty("PlanProductType");
diff --git a/libs/common/src/billing/models/response/subscription.response.ts b/libs/common/src/billing/models/response/subscription.response.ts
index 35f93cc265..8230d98417 100644
--- a/libs/common/src/billing/models/response/subscription.response.ts
+++ b/libs/common/src/billing/models/response/subscription.response.ts
@@ -75,7 +75,7 @@ export class BillingSubscriptionItemResponse extends BaseResponse {
export class BillingSubscriptionUpcomingInvoiceResponse extends BaseResponse {
date: string;
- amount: number;
+ amount?: number;
constructor(response: any) {
super(response);
diff --git a/libs/common/src/enums/index.ts b/libs/common/src/enums/index.ts
index d51e50ca0f..87a688b856 100644
--- a/libs/common/src/enums/index.ts
+++ b/libs/common/src/enums/index.ts
@@ -16,6 +16,7 @@ export * from "./log-level-type.enum";
export * from "./native-messaging-version.enum";
export * from "./notification-type.enum";
export * from "./product-type.enum";
+export * from "./provider-type.enum";
export * from "./secure-note-type.enum";
export * from "./state-version.enum";
export * from "./storage-location.enum";
diff --git a/libs/common/src/enums/provider-type.enum.ts b/libs/common/src/enums/provider-type.enum.ts
new file mode 100644
index 0000000000..5f81c338f0
--- /dev/null
+++ b/libs/common/src/enums/provider-type.enum.ts
@@ -0,0 +1,4 @@
+export enum ProviderType {
+ Msp = 0,
+ Reseller = 1,
+}
diff --git a/libs/common/src/services/organization-user/organization-user.service.implementation.ts b/libs/common/src/services/organization-user/organization-user.service.implementation.ts
index 37b9da0be5..58cf4edd92 100644
--- a/libs/common/src/services/organization-user/organization-user.service.implementation.ts
+++ b/libs/common/src/services/organization-user/organization-user.service.implementation.ts
@@ -1,6 +1,7 @@
import { ApiService } from "../../abstractions/api.service";
import { OrganizationUserService } from "../../abstractions/organization-user/organization-user.service";
import {
+ OrganizationUserAcceptInitRequest,
OrganizationUserAcceptRequest,
OrganizationUserBulkConfirmRequest,
OrganizationUserConfirmRequest,
@@ -135,6 +136,20 @@ export class OrganizationUserServiceImplementation implements OrganizationUserSe
return new ListResponse(r, OrganizationUserBulkResponse);
}
+ postOrganizationUserAcceptInit(
+ organizationId: string,
+ id: string,
+ request: OrganizationUserAcceptInitRequest
+ ): Promise {
+ return this.apiService.send(
+ "POST",
+ "/organizations/" + organizationId + "/users/" + id + "/accept-init",
+ request,
+ true,
+ false
+ );
+ }
+
postOrganizationUserAccept(
organizationId: string,
id: string,