@bash-app/bash-common 30.372.0 → 30.374.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/utils/__tests__/organizationEntitlements.test.js +66 -27
- package/dist/utils/__tests__/organizationEntitlements.test.js.map +1 -1
- package/dist/utils/organizationEntitlements.d.ts +13 -11
- package/dist/utils/organizationEntitlements.d.ts.map +1 -1
- package/dist/utils/organizationEntitlements.js +32 -19
- package/dist/utils/organizationEntitlements.js.map +1 -1
- package/package.json +1 -1
- package/prisma/schema.prisma +10 -7
- package/src/utils/__tests__/organizationEntitlements.test.ts +79 -30
- package/src/utils/organizationEntitlements.ts +34 -20
|
@@ -5,34 +5,72 @@ describe("organizationHasVanitySubdomain", () => {
|
|
|
5
5
|
expect(organizationHasVanitySubdomain(null)).toBe(false);
|
|
6
6
|
expect(organizationHasVanitySubdomain(undefined)).toBe(false);
|
|
7
7
|
});
|
|
8
|
-
test("
|
|
9
|
-
for (const tier of ["Starter", "
|
|
8
|
+
test("below-Growth tiers are never entitled", () => {
|
|
9
|
+
for (const tier of ["Starter", "Church", "Legacy", null]) {
|
|
10
10
|
expect(organizationHasVanitySubdomain({
|
|
11
11
|
subscriptionTier: tier,
|
|
12
12
|
subscriptionStatus: "Active",
|
|
13
13
|
})).toBe(false);
|
|
14
14
|
}
|
|
15
15
|
});
|
|
16
|
-
test("Enterprise with active or trialing status is entitled", () => {
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
test("Growth or Enterprise with active or trialing status is entitled", () => {
|
|
17
|
+
for (const tier of ["Growth", "Enterprise"]) {
|
|
18
|
+
expect(organizationHasVanitySubdomain({
|
|
19
|
+
subscriptionTier: tier,
|
|
20
|
+
subscriptionStatus: "Active",
|
|
21
|
+
})).toBe(true);
|
|
22
|
+
expect(organizationHasVanitySubdomain({
|
|
23
|
+
subscriptionTier: tier,
|
|
24
|
+
subscriptionStatus: "Trialing",
|
|
25
|
+
})).toBe(true);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
test("Growth or Enterprise with a missing status is treated as active (legacy/ops orgs)", () => {
|
|
29
|
+
for (const tier of ["Growth", "Enterprise"]) {
|
|
30
|
+
expect(organizationHasVanitySubdomain({ subscriptionTier: tier })).toBe(true);
|
|
31
|
+
expect(organizationHasVanitySubdomain({
|
|
32
|
+
subscriptionTier: tier,
|
|
33
|
+
subscriptionStatus: null,
|
|
34
|
+
})).toBe(true);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
test("Growth or Enterprise with a lapsed status is not entitled", () => {
|
|
38
|
+
for (const tier of ["Growth", "Enterprise"]) {
|
|
39
|
+
for (const status of ["Canceled", "PastDue", "Canceling"]) {
|
|
40
|
+
expect(organizationHasVanitySubdomain({
|
|
41
|
+
subscriptionTier: tier,
|
|
42
|
+
subscriptionStatus: status,
|
|
43
|
+
})).toBe(false);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
describe("organizationHasCustomDomainAccess", () => {
|
|
49
|
+
test("null / undefined return false", () => {
|
|
50
|
+
expect(organizationHasCustomDomainAccess(null)).toBe(false);
|
|
51
|
+
expect(organizationHasCustomDomainAccess(undefined)).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
test("Growth is not entitled — custom domains stay Enterprise-only", () => {
|
|
54
|
+
expect(organizationHasCustomDomainAccess({
|
|
55
|
+
subscriptionTier: "Growth",
|
|
19
56
|
subscriptionStatus: "Active",
|
|
20
|
-
})).toBe(
|
|
21
|
-
|
|
57
|
+
})).toBe(false);
|
|
58
|
+
});
|
|
59
|
+
test("Enterprise with active or trialing status is entitled, regardless of enterpriseSalesProvisioned", () => {
|
|
60
|
+
expect(organizationHasCustomDomainAccess({
|
|
22
61
|
subscriptionTier: "Enterprise",
|
|
23
|
-
subscriptionStatus: "
|
|
62
|
+
subscriptionStatus: "Active",
|
|
63
|
+
enterpriseSalesProvisioned: true,
|
|
24
64
|
})).toBe(true);
|
|
25
|
-
|
|
26
|
-
test("Enterprise with a missing status is treated as active (legacy/ops orgs)", () => {
|
|
27
|
-
expect(organizationHasVanitySubdomain({ subscriptionTier: "Enterprise" })).toBe(true);
|
|
28
|
-
expect(organizationHasVanitySubdomain({
|
|
65
|
+
expect(organizationHasCustomDomainAccess({
|
|
29
66
|
subscriptionTier: "Enterprise",
|
|
30
|
-
subscriptionStatus:
|
|
67
|
+
subscriptionStatus: "Active",
|
|
68
|
+
enterpriseSalesProvisioned: false,
|
|
31
69
|
})).toBe(true);
|
|
32
70
|
});
|
|
33
71
|
test("Enterprise with a lapsed status is not entitled", () => {
|
|
34
72
|
for (const status of ["Canceled", "PastDue", "Canceling"]) {
|
|
35
|
-
expect(
|
|
73
|
+
expect(organizationHasCustomDomainAccess({
|
|
36
74
|
subscriptionTier: "Enterprise",
|
|
37
75
|
subscriptionStatus: status,
|
|
38
76
|
})).toBe(false);
|
|
@@ -47,8 +85,9 @@ describe("enterprise platform gates (audit logs, SSO, procurement, API access)",
|
|
|
47
85
|
subscriptionStatus: "Active",
|
|
48
86
|
enterpriseSalesProvisioned: true,
|
|
49
87
|
};
|
|
50
|
-
//
|
|
51
|
-
|
|
88
|
+
// Not sales-provisioned (e.g. legacy data, or ops hasn't flipped the flag
|
|
89
|
+
// yet): Enterprise tier alone never grants the sales-gated platform surface.
|
|
90
|
+
const nonSalesProvisionedEnterprise = {
|
|
52
91
|
subscriptionTier: "Enterprise",
|
|
53
92
|
subscriptionStatus: "Active",
|
|
54
93
|
enterpriseSalesProvisioned: false,
|
|
@@ -56,7 +95,7 @@ describe("enterprise platform gates (audit logs, SSO, procurement, API access)",
|
|
|
56
95
|
const growthActive = { subscriptionTier: "Growth", subscriptionStatus: "Active" };
|
|
57
96
|
test("organizationHasEnterprisePlatformAccess requires enterpriseSalesProvisioned", () => {
|
|
58
97
|
expect(organizationHasEnterprisePlatformAccess(salesProvisionedEnterprise)).toBe(true);
|
|
59
|
-
expect(organizationHasEnterprisePlatformAccess(
|
|
98
|
+
expect(organizationHasEnterprisePlatformAccess(nonSalesProvisionedEnterprise)).toBe(false);
|
|
60
99
|
expect(organizationHasEnterprisePlatformAccess(growthActive)).toBe(false);
|
|
61
100
|
expect(organizationHasEnterprisePlatformAccess(null)).toBe(false);
|
|
62
101
|
expect(organizationHasEnterprisePlatformAccess(undefined)).toBe(false);
|
|
@@ -69,40 +108,40 @@ describe("enterprise platform gates (audit logs, SSO, procurement, API access)",
|
|
|
69
108
|
});
|
|
70
109
|
test("organizationHasAuditLogAccess follows the shared enterprise gate", () => {
|
|
71
110
|
expect(organizationHasAuditLogAccess(salesProvisionedEnterprise)).toBe(true);
|
|
72
|
-
expect(organizationHasAuditLogAccess(
|
|
111
|
+
expect(organizationHasAuditLogAccess(nonSalesProvisionedEnterprise)).toBe(false);
|
|
73
112
|
expect(organizationHasAuditLogAccess(growthActive)).toBe(false);
|
|
74
113
|
});
|
|
75
114
|
test("organizationHasSsoAccess follows the shared enterprise gate", () => {
|
|
76
115
|
expect(organizationHasSsoAccess(salesProvisionedEnterprise)).toBe(true);
|
|
77
|
-
expect(organizationHasSsoAccess(
|
|
116
|
+
expect(organizationHasSsoAccess(nonSalesProvisionedEnterprise)).toBe(false);
|
|
78
117
|
expect(organizationHasSsoAccess(growthActive)).toBe(false);
|
|
79
118
|
});
|
|
80
119
|
test("organizationHasProcurementAccess follows the shared enterprise gate", () => {
|
|
81
120
|
expect(organizationHasProcurementAccess(salesProvisionedEnterprise)).toBe(true);
|
|
82
|
-
expect(organizationHasProcurementAccess(
|
|
121
|
+
expect(organizationHasProcurementAccess(nonSalesProvisionedEnterprise)).toBe(false);
|
|
83
122
|
expect(organizationHasProcurementAccess(growthActive)).toBe(false);
|
|
84
123
|
});
|
|
85
124
|
test("organizationHasApiAccess follows the shared enterprise gate", () => {
|
|
86
125
|
expect(organizationHasApiAccess(salesProvisionedEnterprise)).toBe(true);
|
|
87
|
-
expect(organizationHasApiAccess(
|
|
126
|
+
expect(organizationHasApiAccess(nonSalesProvisionedEnterprise)).toBe(false);
|
|
88
127
|
expect(organizationHasApiAccess(growthActive)).toBe(false);
|
|
89
128
|
});
|
|
90
129
|
test("organizationHasComplianceAccess follows the shared enterprise gate", () => {
|
|
91
130
|
expect(organizationHasComplianceAccess(salesProvisionedEnterprise)).toBe(true);
|
|
92
|
-
expect(organizationHasComplianceAccess(
|
|
131
|
+
expect(organizationHasComplianceAccess(nonSalesProvisionedEnterprise)).toBe(false);
|
|
93
132
|
expect(organizationHasComplianceAccess(growthActive)).toBe(false);
|
|
94
133
|
});
|
|
95
|
-
test("organizationHasCustomDomainAccess
|
|
134
|
+
test("organizationHasCustomDomainAccess does not require the sales flag, but does require Enterprise (not Growth)", () => {
|
|
96
135
|
expect(organizationHasCustomDomainAccess(salesProvisionedEnterprise)).toBe(true);
|
|
97
|
-
expect(organizationHasCustomDomainAccess(
|
|
136
|
+
expect(organizationHasCustomDomainAccess(nonSalesProvisionedEnterprise)).toBe(true);
|
|
98
137
|
expect(organizationHasCustomDomainAccess(growthActive)).toBe(false);
|
|
99
138
|
expect(organizationHasCustomDomainAccess(null)).toBe(false);
|
|
100
139
|
expect(organizationHasCustomDomainAccess(undefined)).toBe(false);
|
|
101
140
|
});
|
|
102
|
-
test("organizationHasVanitySubdomain
|
|
141
|
+
test("organizationHasVanitySubdomain does not require the sales flag, and Growth is entitled too", () => {
|
|
103
142
|
expect(organizationHasVanitySubdomain(salesProvisionedEnterprise)).toBe(true);
|
|
104
|
-
expect(organizationHasVanitySubdomain(
|
|
105
|
-
expect(organizationHasVanitySubdomain(growthActive)).toBe(
|
|
143
|
+
expect(organizationHasVanitySubdomain(nonSalesProvisionedEnterprise)).toBe(true);
|
|
144
|
+
expect(organizationHasVanitySubdomain(growthActive)).toBe(true);
|
|
106
145
|
});
|
|
107
146
|
});
|
|
108
147
|
//# sourceMappingURL=organizationEntitlements.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"organizationEntitlements.test.js","sourceRoot":"","sources":["../../../src/utils/__tests__/organizationEntitlements.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAEvD,OAAO,EACL,wBAAwB,EACxB,6BAA6B,EAC7B,+BAA+B,EAC/B,iCAAiC,EACjC,uCAAuC,EACvC,gCAAgC,EAChC,wBAAwB,EACxB,8BAA8B,GAC/B,MAAM,gCAAgC,CAAC;AAExC,QAAQ,CAAC,gCAAgC,EAAE,GAAG,EAAE;IAC9C,IAAI,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACzC,MAAM,CAAC,8BAA8B,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzD,MAAM,CAAC,8BAA8B,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"organizationEntitlements.test.js","sourceRoot":"","sources":["../../../src/utils/__tests__/organizationEntitlements.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -21,11 +21,12 @@ export interface OrganizationEntitlementInput {
|
|
|
21
21
|
enterpriseSalesProvisioned?: boolean | null;
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
|
-
* Vanity subdomains ({slug}.bash.community) and
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
24
|
+
* Vanity subdomains ({slug}.bash.community) are a Growth-tier-and-up perk, not
|
|
25
|
+
* Enterprise-only: unlike custom domains, granting one costs nothing extra
|
|
26
|
+
* (a slug lookup against the existing bash.community CloudFront distribution,
|
|
27
|
+
* no per-org AWS resources) — it's a cheap, visible upgrade incentive to dangle
|
|
28
|
+
* in front of Starter orgs. See {@link organizationHasCustomDomainAccess} for
|
|
29
|
+
* the pricier, Enterprise-only white-label surface.
|
|
29
30
|
*/
|
|
30
31
|
export declare function organizationHasVanitySubdomain(org: OrganizationEntitlementInput | null | undefined): boolean;
|
|
31
32
|
/**
|
|
@@ -50,13 +51,14 @@ export declare function organizationHasComplianceAccess(org: OrganizationEntitle
|
|
|
50
51
|
/**
|
|
51
52
|
* Custom domains (white-label tier): real DNS (e.g. members.acme.com) provisioned
|
|
52
53
|
* via AWS CloudFront SaaS Manager Distribution Tenants, plus the "Powered by Bash"
|
|
53
|
-
* removal toggle.
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
54
|
+
* removal toggle. Unlike {@link organizationHasVanitySubdomain}, this needs a real
|
|
55
|
+
* per-org CloudFront Distribution Tenant + managed cert + the org's own DNS work —
|
|
56
|
+
* actual infra cost and complexity per org — so it stays Enterprise-only (today
|
|
57
|
+
* always sales-provisioned, since self-serve Enterprise checkout is blocked; see
|
|
58
|
+
* docs/ENTERPRISE-B2B-SALES-PLAYBOOK.md). Does NOT require
|
|
57
59
|
* {@link organizationHasEnterprisePlatformAccess}'s `enterpriseSalesProvisioned`
|
|
58
|
-
* flag, so a fast-follow self-serve Enterprise checkout could unlock this
|
|
59
|
-
*
|
|
60
|
+
* flag, so a fast-follow self-serve Enterprise checkout could unlock this without
|
|
61
|
+
* the sales-gated platform surface (SSO, audit logs, procurement, compliance, API).
|
|
60
62
|
*/
|
|
61
63
|
export declare function organizationHasCustomDomainAccess(org: OrganizationEntitlementInput | null | undefined): boolean;
|
|
62
64
|
//# sourceMappingURL=organizationEntitlements.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"organizationEntitlements.d.ts","sourceRoot":"","sources":["../../src/utils/organizationEntitlements.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,2EAA2E;AAC3E,eAAO,MAAM,gCAAgC,iCAGnC,CAAC;AAEX,MAAM,WAAW,4BAA4B;IAC3C,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC;;;;;;;OAOG;IACH,0BAA0B,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC7C;
|
|
1
|
+
{"version":3,"file":"organizationEntitlements.d.ts","sourceRoot":"","sources":["../../src/utils/organizationEntitlements.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,2EAA2E;AAC3E,eAAO,MAAM,gCAAgC,iCAGnC,CAAC;AAEX,MAAM,WAAW,4BAA4B;IAC3C,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC;;;;;;;OAOG;IACH,0BAA0B,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;CAC7C;AA4BD;;;;;;;GAOG;AACH,wBAAgB,8BAA8B,CAC5C,GAAG,EAAE,4BAA4B,GAAG,IAAI,GAAG,SAAS,GACnD,OAAO,CAET;AAED;;;;;;;GAOG;AACH,wBAAgB,uCAAuC,CACrD,GAAG,EAAE,4BAA4B,GAAG,IAAI,GAAG,SAAS,GACnD,OAAO,CAET;AAED,+CAA+C;AAC/C,wBAAgB,6BAA6B,CAC3C,GAAG,EAAE,4BAA4B,GAAG,IAAI,GAAG,SAAS,GACnD,OAAO,CAET;AAED,0BAA0B;AAC1B,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,4BAA4B,GAAG,IAAI,GAAG,SAAS,GACnD,OAAO,CAET;AAED,+DAA+D;AAC/D,wBAAgB,gCAAgC,CAC9C,GAAG,EAAE,4BAA4B,GAAG,IAAI,GAAG,SAAS,GACnD,OAAO,CAET;AAED,iEAAiE;AACjE,wBAAgB,wBAAwB,CACtC,GAAG,EAAE,4BAA4B,GAAG,IAAI,GAAG,SAAS,GACnD,OAAO,CAET;AAED,+EAA+E;AAC/E,wBAAgB,+BAA+B,CAC7C,GAAG,EAAE,4BAA4B,GAAG,IAAI,GAAG,SAAS,GACnD,OAAO,CAET;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iCAAiC,CAC/C,GAAG,EAAE,4BAA4B,GAAG,IAAI,GAAG,SAAS,GACnD,OAAO,CAET"}
|
|
@@ -10,27 +10,39 @@ export const ACTIVE_ORG_SUBSCRIPTION_STATUSES = [
|
|
|
10
10
|
"Active",
|
|
11
11
|
"Trialing",
|
|
12
12
|
];
|
|
13
|
+
// Treat a missing status as active: legacy/ops-provisioned orgs may predate
|
|
14
|
+
// subscriptionStatus being populated, and we don't want to silently drop
|
|
15
|
+
// their entitlements.
|
|
16
|
+
function isActiveOrTrialingStatus(status) {
|
|
17
|
+
if (status == null)
|
|
18
|
+
return true;
|
|
19
|
+
return ACTIVE_ORG_SUBSCRIPTION_STATUSES.includes(status);
|
|
20
|
+
}
|
|
13
21
|
function hasActiveEnterpriseTier(org) {
|
|
14
22
|
if (!org)
|
|
15
23
|
return false;
|
|
16
24
|
if (org.subscriptionTier !== "Enterprise")
|
|
17
25
|
return false;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (org
|
|
22
|
-
return
|
|
23
|
-
|
|
26
|
+
return isActiveOrTrialingStatus(org.subscriptionStatus);
|
|
27
|
+
}
|
|
28
|
+
function hasActiveGrowthOrAboveTier(org) {
|
|
29
|
+
if (!org)
|
|
30
|
+
return false;
|
|
31
|
+
if (org.subscriptionTier !== "Growth" && org.subscriptionTier !== "Enterprise") {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
return isActiveOrTrialingStatus(org.subscriptionStatus);
|
|
24
35
|
}
|
|
25
36
|
/**
|
|
26
|
-
* Vanity subdomains ({slug}.bash.community) and
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
37
|
+
* Vanity subdomains ({slug}.bash.community) are a Growth-tier-and-up perk, not
|
|
38
|
+
* Enterprise-only: unlike custom domains, granting one costs nothing extra
|
|
39
|
+
* (a slug lookup against the existing bash.community CloudFront distribution,
|
|
40
|
+
* no per-org AWS resources) — it's a cheap, visible upgrade incentive to dangle
|
|
41
|
+
* in front of Starter orgs. See {@link organizationHasCustomDomainAccess} for
|
|
42
|
+
* the pricier, Enterprise-only white-label surface.
|
|
31
43
|
*/
|
|
32
44
|
export function organizationHasVanitySubdomain(org) {
|
|
33
|
-
return
|
|
45
|
+
return hasActiveGrowthOrAboveTier(org);
|
|
34
46
|
}
|
|
35
47
|
/**
|
|
36
48
|
* Shared gate for the enterprise platform surface: org-scoped audit logs,
|
|
@@ -66,15 +78,16 @@ export function organizationHasComplianceAccess(org) {
|
|
|
66
78
|
/**
|
|
67
79
|
* Custom domains (white-label tier): real DNS (e.g. members.acme.com) provisioned
|
|
68
80
|
* via AWS CloudFront SaaS Manager Distribution Tenants, plus the "Powered by Bash"
|
|
69
|
-
* removal toggle.
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
81
|
+
* removal toggle. Unlike {@link organizationHasVanitySubdomain}, this needs a real
|
|
82
|
+
* per-org CloudFront Distribution Tenant + managed cert + the org's own DNS work —
|
|
83
|
+
* actual infra cost and complexity per org — so it stays Enterprise-only (today
|
|
84
|
+
* always sales-provisioned, since self-serve Enterprise checkout is blocked; see
|
|
85
|
+
* docs/ENTERPRISE-B2B-SALES-PLAYBOOK.md). Does NOT require
|
|
73
86
|
* {@link organizationHasEnterprisePlatformAccess}'s `enterpriseSalesProvisioned`
|
|
74
|
-
* flag, so a fast-follow self-serve Enterprise checkout could unlock this
|
|
75
|
-
*
|
|
87
|
+
* flag, so a fast-follow self-serve Enterprise checkout could unlock this without
|
|
88
|
+
* the sales-gated platform surface (SSO, audit logs, procurement, compliance, API).
|
|
76
89
|
*/
|
|
77
90
|
export function organizationHasCustomDomainAccess(org) {
|
|
78
|
-
return
|
|
91
|
+
return hasActiveEnterpriseTier(org);
|
|
79
92
|
}
|
|
80
93
|
//# sourceMappingURL=organizationEntitlements.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"organizationEntitlements.js","sourceRoot":"","sources":["../../src/utils/organizationEntitlements.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,2EAA2E;AAC3E,MAAM,CAAC,MAAM,gCAAgC,GAAG;IAC9C,QAAQ;IACR,UAAU;CACF,CAAC;AAgBX,SAAS,uBAAuB,CAC9B,GAAoD;IAEpD,IAAI,CAAC,GAAG;QAAE,OAAO,KAAK,CAAC;IACvB,IAAI,GAAG,CAAC,gBAAgB,KAAK,YAAY;QAAE,OAAO,KAAK,CAAC;IACxD
|
|
1
|
+
{"version":3,"file":"organizationEntitlements.js","sourceRoot":"","sources":["../../src/utils/organizationEntitlements.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,2EAA2E;AAC3E,MAAM,CAAC,MAAM,gCAAgC,GAAG;IAC9C,QAAQ;IACR,UAAU;CACF,CAAC;AAgBX,4EAA4E;AAC5E,yEAAyE;AACzE,sBAAsB;AACtB,SAAS,wBAAwB,CAAC,MAAsB;IACtD,IAAI,MAAM,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC;IAChC,OAAQ,gCAAsD,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAClF,CAAC;AAED,SAAS,uBAAuB,CAC9B,GAAoD;IAEpD,IAAI,CAAC,GAAG;QAAE,OAAO,KAAK,CAAC;IACvB,IAAI,GAAG,CAAC,gBAAgB,KAAK,YAAY;QAAE,OAAO,KAAK,CAAC;IACxD,OAAO,wBAAwB,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;AAC1D,CAAC;AAED,SAAS,0BAA0B,CACjC,GAAoD;IAEpD,IAAI,CAAC,GAAG;QAAE,OAAO,KAAK,CAAC;IACvB,IAAI,GAAG,CAAC,gBAAgB,KAAK,QAAQ,IAAI,GAAG,CAAC,gBAAgB,KAAK,YAAY,EAAE,CAAC;QAC/E,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,wBAAwB,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;AAC1D,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,8BAA8B,CAC5C,GAAoD;IAEpD,OAAO,0BAA0B,CAAC,GAAG,CAAC,CAAC;AACzC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,uCAAuC,CACrD,GAAoD;IAEpD,OAAO,uBAAuB,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,0BAA0B,KAAK,IAAI,CAAC;AAClF,CAAC;AAED,+CAA+C;AAC/C,MAAM,UAAU,6BAA6B,CAC3C,GAAoD;IAEpD,OAAO,uCAAuC,CAAC,GAAG,CAAC,CAAC;AACtD,CAAC;AAED,0BAA0B;AAC1B,MAAM,UAAU,wBAAwB,CACtC,GAAoD;IAEpD,OAAO,uCAAuC,CAAC,GAAG,CAAC,CAAC;AACtD,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,gCAAgC,CAC9C,GAAoD;IAEpD,OAAO,uCAAuC,CAAC,GAAG,CAAC,CAAC;AACtD,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,wBAAwB,CACtC,GAAoD;IAEpD,OAAO,uCAAuC,CAAC,GAAG,CAAC,CAAC;AACtD,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,+BAA+B,CAC7C,GAAoD;IAEpD,OAAO,uCAAuC,CAAC,GAAG,CAAC,CAAC;AACtD,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,iCAAiC,CAC/C,GAAoD;IAEpD,OAAO,uBAAuB,CAAC,GAAG,CAAC,CAAC;AACtC,CAAC"}
|
package/package.json
CHANGED
package/prisma/schema.prisma
CHANGED
|
@@ -6026,13 +6026,16 @@ model Organization {
|
|
|
6026
6026
|
/// "Standard" | "Priority" | "Dedicated" — drives named-AM banner + support routing copy.
|
|
6027
6027
|
supportTier String @default("Standard")
|
|
6028
6028
|
/// True only when ops set subscriptionTier="Enterprise" via the admin
|
|
6029
|
-
/// enterprise-provisioning endpoint (a signed deal)
|
|
6030
|
-
///
|
|
6031
|
-
///
|
|
6032
|
-
/// SSO/audit-log/procurement/
|
|
6033
|
-
///
|
|
6034
|
-
/// (
|
|
6035
|
-
///
|
|
6029
|
+
/// enterprise-provisioning endpoint (a signed deal) — the only way to reach
|
|
6030
|
+
/// subscriptionTier="Enterprise" today, since self-serve Enterprise checkout
|
|
6031
|
+
/// is blocked (see docs/ENTERPRISE-B2B-SALES-PLAYBOOK.md).
|
|
6032
|
+
/// organizationHasEnterprisePlatformAccess() and the SSO/audit-log/procurement/
|
|
6033
|
+
/// compliance/API entitlements below require this flag in addition to the
|
|
6034
|
+
/// Enterprise tier. organizationHasCustomDomainAccess() (custom domains,
|
|
6035
|
+
/// "Powered by Bash" removal) requires the Enterprise tier but NOT this flag,
|
|
6036
|
+
/// so a fast-follow self-serve Enterprise checkout could unlock white-label
|
|
6037
|
+
/// without the sales-gated platform surface. organizationHasVanitySubdomain()
|
|
6038
|
+
/// is a separate, cheaper perk available to Growth tier and up.
|
|
6036
6039
|
enterpriseSalesProvisioned Boolean @default(false)
|
|
6037
6040
|
|
|
6038
6041
|
/// SSO / identity (Organization Enterprise only). `ssoEnforced` blocks password
|
|
@@ -17,8 +17,8 @@ describe("organizationHasVanitySubdomain", () => {
|
|
|
17
17
|
expect(organizationHasVanitySubdomain(undefined)).toBe(false);
|
|
18
18
|
});
|
|
19
19
|
|
|
20
|
-
test("
|
|
21
|
-
for (const tier of ["Starter", "
|
|
20
|
+
test("below-Growth tiers are never entitled", () => {
|
|
21
|
+
for (const tier of ["Starter", "Church", "Legacy", null]) {
|
|
22
22
|
expect(
|
|
23
23
|
organizationHasVanitySubdomain({
|
|
24
24
|
subscriptionTier: tier,
|
|
@@ -28,29 +28,77 @@ describe("organizationHasVanitySubdomain", () => {
|
|
|
28
28
|
}
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
-
test("Enterprise with active or trialing status is entitled", () => {
|
|
31
|
+
test("Growth or Enterprise with active or trialing status is entitled", () => {
|
|
32
|
+
for (const tier of ["Growth", "Enterprise"]) {
|
|
33
|
+
expect(
|
|
34
|
+
organizationHasVanitySubdomain({
|
|
35
|
+
subscriptionTier: tier,
|
|
36
|
+
subscriptionStatus: "Active",
|
|
37
|
+
})
|
|
38
|
+
).toBe(true);
|
|
39
|
+
expect(
|
|
40
|
+
organizationHasVanitySubdomain({
|
|
41
|
+
subscriptionTier: tier,
|
|
42
|
+
subscriptionStatus: "Trialing",
|
|
43
|
+
})
|
|
44
|
+
).toBe(true);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("Growth or Enterprise with a missing status is treated as active (legacy/ops orgs)", () => {
|
|
49
|
+
for (const tier of ["Growth", "Enterprise"]) {
|
|
50
|
+
expect(organizationHasVanitySubdomain({ subscriptionTier: tier })).toBe(true);
|
|
51
|
+
expect(
|
|
52
|
+
organizationHasVanitySubdomain({
|
|
53
|
+
subscriptionTier: tier,
|
|
54
|
+
subscriptionStatus: null,
|
|
55
|
+
})
|
|
56
|
+
).toBe(true);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("Growth or Enterprise with a lapsed status is not entitled", () => {
|
|
61
|
+
for (const tier of ["Growth", "Enterprise"]) {
|
|
62
|
+
for (const status of ["Canceled", "PastDue", "Canceling"]) {
|
|
63
|
+
expect(
|
|
64
|
+
organizationHasVanitySubdomain({
|
|
65
|
+
subscriptionTier: tier,
|
|
66
|
+
subscriptionStatus: status,
|
|
67
|
+
})
|
|
68
|
+
).toBe(false);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("organizationHasCustomDomainAccess", () => {
|
|
75
|
+
test("null / undefined return false", () => {
|
|
76
|
+
expect(organizationHasCustomDomainAccess(null)).toBe(false);
|
|
77
|
+
expect(organizationHasCustomDomainAccess(undefined)).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("Growth is not entitled — custom domains stay Enterprise-only", () => {
|
|
32
81
|
expect(
|
|
33
|
-
|
|
34
|
-
subscriptionTier: "
|
|
82
|
+
organizationHasCustomDomainAccess({
|
|
83
|
+
subscriptionTier: "Growth",
|
|
35
84
|
subscriptionStatus: "Active",
|
|
36
85
|
})
|
|
37
|
-
).toBe(
|
|
38
|
-
expect(
|
|
39
|
-
organizationHasVanitySubdomain({
|
|
40
|
-
subscriptionTier: "Enterprise",
|
|
41
|
-
subscriptionStatus: "Trialing",
|
|
42
|
-
})
|
|
43
|
-
).toBe(true);
|
|
86
|
+
).toBe(false);
|
|
44
87
|
});
|
|
45
88
|
|
|
46
|
-
test("Enterprise with
|
|
89
|
+
test("Enterprise with active or trialing status is entitled, regardless of enterpriseSalesProvisioned", () => {
|
|
47
90
|
expect(
|
|
48
|
-
|
|
91
|
+
organizationHasCustomDomainAccess({
|
|
92
|
+
subscriptionTier: "Enterprise",
|
|
93
|
+
subscriptionStatus: "Active",
|
|
94
|
+
enterpriseSalesProvisioned: true,
|
|
95
|
+
})
|
|
49
96
|
).toBe(true);
|
|
50
97
|
expect(
|
|
51
|
-
|
|
98
|
+
organizationHasCustomDomainAccess({
|
|
52
99
|
subscriptionTier: "Enterprise",
|
|
53
|
-
subscriptionStatus:
|
|
100
|
+
subscriptionStatus: "Active",
|
|
101
|
+
enterpriseSalesProvisioned: false,
|
|
54
102
|
})
|
|
55
103
|
).toBe(true);
|
|
56
104
|
});
|
|
@@ -58,7 +106,7 @@ describe("organizationHasVanitySubdomain", () => {
|
|
|
58
106
|
test("Enterprise with a lapsed status is not entitled", () => {
|
|
59
107
|
for (const status of ["Canceled", "PastDue", "Canceling"]) {
|
|
60
108
|
expect(
|
|
61
|
-
|
|
109
|
+
organizationHasCustomDomainAccess({
|
|
62
110
|
subscriptionTier: "Enterprise",
|
|
63
111
|
subscriptionStatus: status,
|
|
64
112
|
})
|
|
@@ -75,8 +123,9 @@ describe("enterprise platform gates (audit logs, SSO, procurement, API access)",
|
|
|
75
123
|
subscriptionStatus: "Active",
|
|
76
124
|
enterpriseSalesProvisioned: true,
|
|
77
125
|
};
|
|
78
|
-
//
|
|
79
|
-
|
|
126
|
+
// Not sales-provisioned (e.g. legacy data, or ops hasn't flipped the flag
|
|
127
|
+
// yet): Enterprise tier alone never grants the sales-gated platform surface.
|
|
128
|
+
const nonSalesProvisionedEnterprise = {
|
|
80
129
|
subscriptionTier: "Enterprise",
|
|
81
130
|
subscriptionStatus: "Active",
|
|
82
131
|
enterpriseSalesProvisioned: false,
|
|
@@ -85,7 +134,7 @@ describe("enterprise platform gates (audit logs, SSO, procurement, API access)",
|
|
|
85
134
|
|
|
86
135
|
test("organizationHasEnterprisePlatformAccess requires enterpriseSalesProvisioned", () => {
|
|
87
136
|
expect(organizationHasEnterprisePlatformAccess(salesProvisionedEnterprise)).toBe(true);
|
|
88
|
-
expect(organizationHasEnterprisePlatformAccess(
|
|
137
|
+
expect(organizationHasEnterprisePlatformAccess(nonSalesProvisionedEnterprise)).toBe(false);
|
|
89
138
|
expect(organizationHasEnterprisePlatformAccess(growthActive)).toBe(false);
|
|
90
139
|
expect(organizationHasEnterprisePlatformAccess(null)).toBe(false);
|
|
91
140
|
expect(organizationHasEnterprisePlatformAccess(undefined)).toBe(false);
|
|
@@ -102,45 +151,45 @@ describe("enterprise platform gates (audit logs, SSO, procurement, API access)",
|
|
|
102
151
|
|
|
103
152
|
test("organizationHasAuditLogAccess follows the shared enterprise gate", () => {
|
|
104
153
|
expect(organizationHasAuditLogAccess(salesProvisionedEnterprise)).toBe(true);
|
|
105
|
-
expect(organizationHasAuditLogAccess(
|
|
154
|
+
expect(organizationHasAuditLogAccess(nonSalesProvisionedEnterprise)).toBe(false);
|
|
106
155
|
expect(organizationHasAuditLogAccess(growthActive)).toBe(false);
|
|
107
156
|
});
|
|
108
157
|
|
|
109
158
|
test("organizationHasSsoAccess follows the shared enterprise gate", () => {
|
|
110
159
|
expect(organizationHasSsoAccess(salesProvisionedEnterprise)).toBe(true);
|
|
111
|
-
expect(organizationHasSsoAccess(
|
|
160
|
+
expect(organizationHasSsoAccess(nonSalesProvisionedEnterprise)).toBe(false);
|
|
112
161
|
expect(organizationHasSsoAccess(growthActive)).toBe(false);
|
|
113
162
|
});
|
|
114
163
|
|
|
115
164
|
test("organizationHasProcurementAccess follows the shared enterprise gate", () => {
|
|
116
165
|
expect(organizationHasProcurementAccess(salesProvisionedEnterprise)).toBe(true);
|
|
117
|
-
expect(organizationHasProcurementAccess(
|
|
166
|
+
expect(organizationHasProcurementAccess(nonSalesProvisionedEnterprise)).toBe(false);
|
|
118
167
|
expect(organizationHasProcurementAccess(growthActive)).toBe(false);
|
|
119
168
|
});
|
|
120
169
|
|
|
121
170
|
test("organizationHasApiAccess follows the shared enterprise gate", () => {
|
|
122
171
|
expect(organizationHasApiAccess(salesProvisionedEnterprise)).toBe(true);
|
|
123
|
-
expect(organizationHasApiAccess(
|
|
172
|
+
expect(organizationHasApiAccess(nonSalesProvisionedEnterprise)).toBe(false);
|
|
124
173
|
expect(organizationHasApiAccess(growthActive)).toBe(false);
|
|
125
174
|
});
|
|
126
175
|
|
|
127
176
|
test("organizationHasComplianceAccess follows the shared enterprise gate", () => {
|
|
128
177
|
expect(organizationHasComplianceAccess(salesProvisionedEnterprise)).toBe(true);
|
|
129
|
-
expect(organizationHasComplianceAccess(
|
|
178
|
+
expect(organizationHasComplianceAccess(nonSalesProvisionedEnterprise)).toBe(false);
|
|
130
179
|
expect(organizationHasComplianceAccess(growthActive)).toBe(false);
|
|
131
180
|
});
|
|
132
181
|
|
|
133
|
-
test("organizationHasCustomDomainAccess
|
|
182
|
+
test("organizationHasCustomDomainAccess does not require the sales flag, but does require Enterprise (not Growth)", () => {
|
|
134
183
|
expect(organizationHasCustomDomainAccess(salesProvisionedEnterprise)).toBe(true);
|
|
135
|
-
expect(organizationHasCustomDomainAccess(
|
|
184
|
+
expect(organizationHasCustomDomainAccess(nonSalesProvisionedEnterprise)).toBe(true);
|
|
136
185
|
expect(organizationHasCustomDomainAccess(growthActive)).toBe(false);
|
|
137
186
|
expect(organizationHasCustomDomainAccess(null)).toBe(false);
|
|
138
187
|
expect(organizationHasCustomDomainAccess(undefined)).toBe(false);
|
|
139
188
|
});
|
|
140
189
|
|
|
141
|
-
test("organizationHasVanitySubdomain
|
|
190
|
+
test("organizationHasVanitySubdomain does not require the sales flag, and Growth is entitled too", () => {
|
|
142
191
|
expect(organizationHasVanitySubdomain(salesProvisionedEnterprise)).toBe(true);
|
|
143
|
-
expect(organizationHasVanitySubdomain(
|
|
144
|
-
expect(organizationHasVanitySubdomain(growthActive)).toBe(
|
|
192
|
+
expect(organizationHasVanitySubdomain(nonSalesProvisionedEnterprise)).toBe(true);
|
|
193
|
+
expect(organizationHasVanitySubdomain(growthActive)).toBe(true);
|
|
145
194
|
});
|
|
146
195
|
});
|
|
@@ -26,31 +26,44 @@ export interface OrganizationEntitlementInput {
|
|
|
26
26
|
enterpriseSalesProvisioned?: boolean | null;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
// Treat a missing status as active: legacy/ops-provisioned orgs may predate
|
|
30
|
+
// subscriptionStatus being populated, and we don't want to silently drop
|
|
31
|
+
// their entitlements.
|
|
32
|
+
function isActiveOrTrialingStatus(status?: string | null): boolean {
|
|
33
|
+
if (status == null) return true;
|
|
34
|
+
return (ACTIVE_ORG_SUBSCRIPTION_STATUSES as readonly string[]).includes(status);
|
|
35
|
+
}
|
|
36
|
+
|
|
29
37
|
function hasActiveEnterpriseTier(
|
|
30
38
|
org: OrganizationEntitlementInput | null | undefined
|
|
31
39
|
): boolean {
|
|
32
40
|
if (!org) return false;
|
|
33
41
|
if (org.subscriptionTier !== "Enterprise") return false;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
);
|
|
42
|
+
return isActiveOrTrialingStatus(org.subscriptionStatus);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function hasActiveGrowthOrAboveTier(
|
|
46
|
+
org: OrganizationEntitlementInput | null | undefined
|
|
47
|
+
): boolean {
|
|
48
|
+
if (!org) return false;
|
|
49
|
+
if (org.subscriptionTier !== "Growth" && org.subscriptionTier !== "Enterprise") {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
return isActiveOrTrialingStatus(org.subscriptionStatus);
|
|
41
53
|
}
|
|
42
54
|
|
|
43
55
|
/**
|
|
44
|
-
* Vanity subdomains ({slug}.bash.community) and
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
56
|
+
* Vanity subdomains ({slug}.bash.community) are a Growth-tier-and-up perk, not
|
|
57
|
+
* Enterprise-only: unlike custom domains, granting one costs nothing extra
|
|
58
|
+
* (a slug lookup against the existing bash.community CloudFront distribution,
|
|
59
|
+
* no per-org AWS resources) — it's a cheap, visible upgrade incentive to dangle
|
|
60
|
+
* in front of Starter orgs. See {@link organizationHasCustomDomainAccess} for
|
|
61
|
+
* the pricier, Enterprise-only white-label surface.
|
|
49
62
|
*/
|
|
50
63
|
export function organizationHasVanitySubdomain(
|
|
51
64
|
org: OrganizationEntitlementInput | null | undefined
|
|
52
65
|
): boolean {
|
|
53
|
-
return
|
|
66
|
+
return hasActiveGrowthOrAboveTier(org);
|
|
54
67
|
}
|
|
55
68
|
|
|
56
69
|
/**
|
|
@@ -105,16 +118,17 @@ export function organizationHasComplianceAccess(
|
|
|
105
118
|
/**
|
|
106
119
|
* Custom domains (white-label tier): real DNS (e.g. members.acme.com) provisioned
|
|
107
120
|
* via AWS CloudFront SaaS Manager Distribution Tenants, plus the "Powered by Bash"
|
|
108
|
-
* removal toggle.
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
121
|
+
* removal toggle. Unlike {@link organizationHasVanitySubdomain}, this needs a real
|
|
122
|
+
* per-org CloudFront Distribution Tenant + managed cert + the org's own DNS work —
|
|
123
|
+
* actual infra cost and complexity per org — so it stays Enterprise-only (today
|
|
124
|
+
* always sales-provisioned, since self-serve Enterprise checkout is blocked; see
|
|
125
|
+
* docs/ENTERPRISE-B2B-SALES-PLAYBOOK.md). Does NOT require
|
|
112
126
|
* {@link organizationHasEnterprisePlatformAccess}'s `enterpriseSalesProvisioned`
|
|
113
|
-
* flag, so a fast-follow self-serve Enterprise checkout could unlock this
|
|
114
|
-
*
|
|
127
|
+
* flag, so a fast-follow self-serve Enterprise checkout could unlock this without
|
|
128
|
+
* the sales-gated platform surface (SSO, audit logs, procurement, compliance, API).
|
|
115
129
|
*/
|
|
116
130
|
export function organizationHasCustomDomainAccess(
|
|
117
131
|
org: OrganizationEntitlementInput | null | undefined
|
|
118
132
|
): boolean {
|
|
119
|
-
return
|
|
133
|
+
return hasActiveEnterpriseTier(org);
|
|
120
134
|
}
|