refactor is licensed and subscribed util functions

This commit is contained in:
miloschwartz
2026-02-09 16:57:41 -08:00
parent e6464929ff
commit dff45748bd
25 changed files with 707 additions and 574 deletions

View File

@@ -107,6 +107,11 @@ export class Config {
process.env.MAXMIND_ASN_PATH = parsedConfig.server.maxmind_asn_path; process.env.MAXMIND_ASN_PATH = parsedConfig.server.maxmind_asn_path;
} }
process.env.DISABLE_ENTERPRISE_FEATURES = parsedConfig.flags
?.disable_enterprise_features
? "true"
: "false";
this.rawConfig = parsedConfig; this.rawConfig = parsedConfig;
} }

View File

@@ -1,3 +1,6 @@
export async function isLicensedOrSubscribed(orgId: string): Promise<boolean> { export async function isLicensedOrSubscribed(
orgId: string,
tiers: string[]
): Promise<boolean> {
return false; return false;
} }

View File

@@ -1,3 +1,6 @@
export async function isSubscribed(orgId: string): Promise<boolean> { export async function isSubscribed(
orgId: string,
tiers: string[]
): Promise<boolean> {
return false; return false;
} }

View File

@@ -331,7 +331,8 @@ export const configSchema = z
disable_local_sites: z.boolean().optional(), disable_local_sites: z.boolean().optional(),
disable_basic_wireguard_sites: z.boolean().optional(), disable_basic_wireguard_sites: z.boolean().optional(),
disable_config_managed_domains: z.boolean().optional(), disable_config_managed_domains: z.boolean().optional(),
disable_product_help_banners: z.boolean().optional() disable_product_help_banners: z.boolean().optional(),
disable_enterprise_features: z.boolean().optional()
}) })
.optional(), .optional(),
dns: z dns: z

View File

@@ -78,6 +78,8 @@ export async function checkOrgAccessPolicy(
} }
} }
// TODO: check that the org is subscribed
// get the needed data // get the needed data
if (!props.org) { if (!props.org) {

View File

@@ -13,16 +13,18 @@
import { build } from "@server/build"; import { build } from "@server/build";
import license from "#private/license/license"; import license from "#private/license/license";
import { getOrgTierData } from "#private/lib/billing"; import { isSubscribed } from "#private/lib/isSubscribed";
export async function isLicensedOrSubscribed(orgId: string): Promise<boolean> { export async function isLicensedOrSubscribed(
orgId: string,
tiers: string[]
): Promise<boolean> {
if (build === "enterprise") { if (build === "enterprise") {
return await license.isUnlocked(); return await license.isUnlocked();
} }
if (build === "saas") { if (build === "saas") {
const { tier, active } = await getOrgTierData(orgId); return isSubscribed(orgId, tiers);
return (tier == "tier1" || tier == "tier2" || tier == "tier3") && active;
} }
return false; return false;

View File

@@ -14,10 +14,14 @@
import { build } from "@server/build"; import { build } from "@server/build";
import { getOrgTierData } from "#private/lib/billing"; import { getOrgTierData } from "#private/lib/billing";
export async function isSubscribed(orgId: string): Promise<boolean> { export async function isSubscribed(
orgId: string,
tiers: string[]
): Promise<boolean> {
if (build === "saas") { if (build === "saas") {
const { tier, active } = await getOrgTierData(orgId); const { tier, active } = await getOrgTierData(orgId);
return (tier == "tier1" || tier == "tier2" || tier == "tier3") && active; const isTier = (tier && tiers.includes(tier)) || false;
return active && isTier;
} }
return false; return false;

View File

@@ -17,17 +17,22 @@ import HttpCode from "@server/types/HttpCode";
import { build } from "@server/build"; import { build } from "@server/build";
import { getOrgTierData } from "#private/lib/billing"; import { getOrgTierData } from "#private/lib/billing";
export async function verifyValidSubscription( export function verifyValidSubscription(tiers: string[]) {
return async function (
req: Request, req: Request,
res: Response, res: Response,
next: NextFunction next: NextFunction
) { ): Promise<any> {
try { try {
if (build != "saas") { if (build != "saas") {
return next(); return next();
} }
const orgId = req.params.orgId || req.body.orgId || req.query.orgId || req.userOrgId; const orgId =
req.params.orgId ||
req.body.orgId ||
req.query.orgId ||
req.userOrgId;
if (!orgId) { if (!orgId) {
return next( return next(
@@ -39,7 +44,8 @@ export async function verifyValidSubscription(
} }
const { tier, active } = await getOrgTierData(orgId); const { tier, active } = await getOrgTierData(orgId);
if ((tier == "tier1" || tier == "tier2" || tier == "tier3") && active) { const isTier = tiers.includes(tier || "");
if (!isTier || !active) {
return next( return next(
createHttpError( createHttpError(
HttpCode.FORBIDDEN, HttpCode.FORBIDDEN,
@@ -57,4 +63,5 @@ export async function verifyValidSubscription(
) )
); );
} }
};
} }

View File

@@ -86,7 +86,7 @@ authenticated.put(
authenticated.post( authenticated.post(
"/org/:orgId/idp/:idpId/oidc", "/org/:orgId/idp/:idpId/oidc",
verifyValidLicense, verifyValidLicense,
verifyValidSubscription, verifyValidSubscription(),
verifyOrgAccess, verifyOrgAccess,
verifyIdpAccess, verifyIdpAccess,
verifyUserHasAction(ActionsEnum.updateIdp), verifyUserHasAction(ActionsEnum.updateIdp),

View File

@@ -195,6 +195,7 @@ export default function CredentialsPage() {
</Alert> </Alert>
)} )}
</SettingsSectionBody> </SettingsSectionBody>
{!env.flags.disableEnterpriseFeatures && (
<SettingsSectionFooter> <SettingsSectionFooter>
<Button <Button
variant="outline" variant="outline"
@@ -216,6 +217,7 @@ export default function CredentialsPage() {
{t("remoteExitNodeRegenerateAndDisconnect")} {t("remoteExitNodeRegenerateAndDisconnect")}
</Button> </Button>
</SettingsSectionFooter> </SettingsSectionFooter>
)}
</SettingsSection> </SettingsSection>
</SettingsContainer> </SettingsContainer>

View File

@@ -183,6 +183,7 @@ export default function CredentialsPage() {
</Alert> </Alert>
)} )}
</SettingsSectionBody> </SettingsSectionBody>
{!env.flags.disableEnterpriseFeatures && (
<SettingsSectionFooter> <SettingsSectionFooter>
<Button <Button
variant="outline" variant="outline"
@@ -204,6 +205,7 @@ export default function CredentialsPage() {
{t("clientRegenerateAndDisconnect")} {t("clientRegenerateAndDisconnect")}
</Button> </Button>
</SettingsSectionFooter> </SettingsSectionFooter>
)}
</SettingsSection> </SettingsSection>
<OlmInstallCommands <OlmInstallCommands

View File

@@ -152,6 +152,7 @@ export default function GeneralPage() {
const [approvalId, setApprovalId] = useState<number | null>(null); const [approvalId, setApprovalId] = useState<number | null>(null);
const [isRefreshing, setIsRefreshing] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false);
const [, startTransition] = useTransition(); const [, startTransition] = useTransition();
const { env } = useEnvContext();
const showApprovalFeatures = build !== "oss" && isPaidUser; const showApprovalFeatures = build !== "oss" && isPaidUser;
@@ -567,6 +568,7 @@ export default function GeneralPage() {
</SettingsSection> </SettingsSection>
)} )}
{!env.flags.disableEnterpriseFeatures && (
<SettingsSection> <SettingsSection>
<SettingsSectionHeader> <SettingsSectionHeader>
<SettingsSectionTitle> <SettingsSectionTitle>
@@ -583,7 +585,8 @@ export default function GeneralPage() {
Object.keys(client.posture).length > 0 ? ( Object.keys(client.posture).length > 0 ? (
<> <>
<InfoSections cols={3}> <InfoSections cols={3}>
{client.posture.biometricsEnabled !== null && {client.posture.biometricsEnabled !==
null &&
client.posture.biometricsEnabled !== client.posture.biometricsEnabled !==
undefined && ( undefined && (
<InfoSection> <InfoSection>
@@ -637,7 +640,8 @@ export default function GeneralPage() {
</InfoSection> </InfoSection>
)} )}
{client.posture.autoUpdatesEnabled !== null && {client.posture.autoUpdatesEnabled !==
null &&
client.posture.autoUpdatesEnabled !== client.posture.autoUpdatesEnabled !==
undefined && ( undefined && (
<InfoSection> <InfoSection>
@@ -675,11 +679,14 @@ export default function GeneralPage() {
{client.posture.windowsAntivirusEnabled !== {client.posture.windowsAntivirusEnabled !==
null && null &&
client.posture.windowsAntivirusEnabled !== client.posture
.windowsAntivirusEnabled !==
undefined && ( undefined && (
<InfoSection> <InfoSection>
<InfoSectionTitle> <InfoSectionTitle>
{t("windowsAntivirusEnabled")} {t(
"windowsAntivirusEnabled"
)}
</InfoSectionTitle> </InfoSectionTitle>
<InfoSectionContent> <InfoSectionContent>
{isPaidUser {isPaidUser
@@ -712,11 +719,14 @@ export default function GeneralPage() {
{client.posture.macosGatekeeperEnabled !== {client.posture.macosGatekeeperEnabled !==
null && null &&
client.posture.macosGatekeeperEnabled !== client.posture
.macosGatekeeperEnabled !==
undefined && ( undefined && (
<InfoSection> <InfoSection>
<InfoSectionTitle> <InfoSectionTitle>
{t("macosGatekeeperEnabled")} {t(
"macosGatekeeperEnabled"
)}
</InfoSectionTitle> </InfoSectionTitle>
<InfoSectionContent> <InfoSectionContent>
{isPaidUser {isPaidUser
@@ -731,11 +741,14 @@ export default function GeneralPage() {
{client.posture.macosFirewallStealthMode !== {client.posture.macosFirewallStealthMode !==
null && null &&
client.posture.macosFirewallStealthMode !== client.posture
.macosFirewallStealthMode !==
undefined && ( undefined && (
<InfoSection> <InfoSection>
<InfoSectionTitle> <InfoSectionTitle>
{t("macosFirewallStealthMode")} {t(
"macosFirewallStealthMode"
)}
</InfoSectionTitle> </InfoSectionTitle>
<InfoSectionContent> <InfoSectionContent>
{isPaidUser {isPaidUser
@@ -748,7 +761,8 @@ export default function GeneralPage() {
</InfoSection> </InfoSection>
)} )}
{client.posture.linuxAppArmorEnabled !== null && {client.posture.linuxAppArmorEnabled !==
null &&
client.posture.linuxAppArmorEnabled !== client.posture.linuxAppArmorEnabled !==
undefined && ( undefined && (
<InfoSection> <InfoSection>
@@ -766,7 +780,8 @@ export default function GeneralPage() {
</InfoSection> </InfoSection>
)} )}
{client.posture.linuxSELinuxEnabled !== null && {client.posture.linuxSELinuxEnabled !==
null &&
client.posture.linuxSELinuxEnabled !== client.posture.linuxSELinuxEnabled !==
undefined && ( undefined && (
<InfoSection> <InfoSection>
@@ -792,6 +807,7 @@ export default function GeneralPage() {
)} )}
</SettingsSectionBody> </SettingsSectionBody>
</SettingsSection> </SettingsSection>
)}
</SettingsContainer> </SettingsContainer>
); );
} }

View File

@@ -10,6 +10,7 @@ import { getTranslations } from "next-intl/server";
import { getCachedOrg } from "@app/lib/api/getCachedOrg"; import { getCachedOrg } from "@app/lib/api/getCachedOrg";
import { getCachedOrgUser } from "@app/lib/api/getCachedOrgUser"; import { getCachedOrgUser } from "@app/lib/api/getCachedOrgUser";
import { build } from "@server/build"; import { build } from "@server/build";
import { pullEnv } from "@app/lib/pullEnv";
type GeneralSettingsProps = { type GeneralSettingsProps = {
children: React.ReactNode; children: React.ReactNode;
@@ -23,6 +24,7 @@ export default async function GeneralSettingsPage({
const { orgId } = await params; const { orgId } = await params;
const user = await verifySession(); const user = await verifySession();
const env = pullEnv();
if (!user) { if (!user) {
redirect(`/`); redirect(`/`);
@@ -56,10 +58,15 @@ export default async function GeneralSettingsPage({
title: t("security"), title: t("security"),
href: `/{orgId}/settings/general/security` href: `/{orgId}/settings/general/security`
}, },
// PaidFeaturesAlert
...(!env.flags.disableEnterpriseFeatures
? [
{ {
title: t("authPage"), title: t("authPage"),
href: `/{orgId}/settings/general/auth-page` href: `/{orgId}/settings/general/auth-page`
} }
]
: [])
]; ];
return ( return (

View File

@@ -102,10 +102,13 @@ type SectionFormProps = {
export default function SecurityPage() { export default function SecurityPage() {
const { org } = useOrgContext(); const { org } = useOrgContext();
const { env } = useEnvContext();
return ( return (
<SettingsContainer> <SettingsContainer>
<LogRetentionSectionForm org={org.org} /> <LogRetentionSectionForm org={org.org} />
{!env.flags.disableEnterpriseFeatures && (
<SecuritySettingsSectionForm org={org.org} /> <SecuritySettingsSectionForm org={org.org} />
)}
</SettingsContainer> </SettingsContainer>
); );
} }
@@ -135,7 +138,8 @@ function LogRetentionSectionForm({ org }: SectionFormProps) {
const { isPaidUser, hasSaasSubscription } = usePaidStatus(); const { isPaidUser, hasSaasSubscription } = usePaidStatus();
const [, formAction, loadingSave] = useActionState(performSave, null); const [, formAction, loadingSave] = useActionState(performSave, null);
const api = createApiClient(useEnvContext()); const { env } = useEnvContext();
const api = createApiClient({ env });
async function performSave() { async function performSave() {
const isValid = await form.trigger(); const isValid = await form.trigger();
@@ -238,6 +242,8 @@ function LogRetentionSectionForm({ org }: SectionFormProps) {
)} )}
/> />
{!env.flags.disableEnterpriseFeatures && (
<>
<PaidFeaturesAlert /> <PaidFeaturesAlert />
<FormField <FormField
@@ -249,13 +255,19 @@ function LogRetentionSectionForm({ org }: SectionFormProps) {
return ( return (
<FormItem> <FormItem>
<FormLabel> <FormLabel>
{t("logRetentionAccessLabel")} {t(
"logRetentionAccessLabel"
)}
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Select <Select
value={field.value.toString()} value={field.value.toString()}
onValueChange={(value) => { onValueChange={(
if (!isDisabled) { value
) => {
if (
!isDisabled
) {
field.onChange( field.onChange(
parseInt( parseInt(
value, value,
@@ -264,7 +276,9 @@ function LogRetentionSectionForm({ org }: SectionFormProps) {
); );
} }
}} }}
disabled={isDisabled} disabled={
isDisabled
}
> >
<SelectTrigger> <SelectTrigger>
<SelectValue <SelectValue
@@ -275,7 +289,9 @@ function LogRetentionSectionForm({ org }: SectionFormProps) {
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{LOG_RETENTION_OPTIONS.map( {LOG_RETENTION_OPTIONS.map(
(option) => ( (
option
) => (
<SelectItem <SelectItem
key={ key={
option.value option.value
@@ -305,13 +321,19 @@ function LogRetentionSectionForm({ org }: SectionFormProps) {
return ( return (
<FormItem> <FormItem>
<FormLabel> <FormLabel>
{t("logRetentionActionLabel")} {t(
"logRetentionActionLabel"
)}
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Select <Select
value={field.value.toString()} value={field.value.toString()}
onValueChange={(value) => { onValueChange={(
if (!isDisabled) { value
) => {
if (
!isDisabled
) {
field.onChange( field.onChange(
parseInt( parseInt(
value, value,
@@ -320,7 +342,9 @@ function LogRetentionSectionForm({ org }: SectionFormProps) {
); );
} }
}} }}
disabled={isDisabled} disabled={
isDisabled
}
> >
<SelectTrigger> <SelectTrigger>
<SelectValue <SelectValue
@@ -331,7 +355,9 @@ function LogRetentionSectionForm({ org }: SectionFormProps) {
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{LOG_RETENTION_OPTIONS.map( {LOG_RETENTION_OPTIONS.map(
(option) => ( (
option
) => (
<SelectItem <SelectItem
key={ key={
option.value option.value
@@ -352,6 +378,8 @@ function LogRetentionSectionForm({ org }: SectionFormProps) {
); );
}} }}
/> />
</>
)}
</form> </form>
</Form> </Form>
</SettingsSectionForm> </SettingsSectionForm>

View File

@@ -163,7 +163,9 @@ function MaintenanceSectionForm({
const isEnterpriseNotLicensed = build === "enterprise" && !isUnlocked(); const isEnterpriseNotLicensed = build === "enterprise" && !isUnlocked();
const isSaasNotSubscribed = const isSaasNotSubscribed =
build === "saas" && !subscription?.isSubscribed(); build === "saas" && !subscription?.isSubscribed();
return isEnterpriseNotLicensed || isSaasNotSubscribed || build === "oss"; return (
isEnterpriseNotLicensed || isSaasNotSubscribed || build === "oss"
);
}; };
if (!resource.http) { if (!resource.http) {
@@ -189,13 +191,14 @@ function MaintenanceSectionForm({
className="space-y-4" className="space-y-4"
id="maintenance-settings-form" id="maintenance-settings-form"
> >
<PaidFeaturesAlert></PaidFeaturesAlert> <PaidFeaturesAlert />
<FormField <FormField
control={maintenanceForm.control} control={maintenanceForm.control}
name="maintenanceModeEnabled" name="maintenanceModeEnabled"
render={({ field }) => { render={({ field }) => {
const isDisabled = const isDisabled =
isSecurityFeatureDisabled() || resource.http === false; isSecurityFeatureDisabled() ||
resource.http === false;
return ( return (
<FormItem> <FormItem>
@@ -741,10 +744,12 @@ export default function GeneralForm() {
</SettingsSectionFooter> </SettingsSectionFooter>
</SettingsSection> </SettingsSection>
{!env.flags.disableEnterpriseFeatures && (
<MaintenanceSectionForm <MaintenanceSectionForm
resource={resource} resource={resource}
updateResource={updateResource} updateResource={updateResource}
/> />
)}
</SettingsContainer> </SettingsContainer>
<Credenza <Credenza

View File

@@ -271,6 +271,7 @@ export default function CredentialsPage() {
</Alert> </Alert>
)} )}
</SettingsSectionBody> </SettingsSectionBody>
{!env.flags.disableEnterpriseFeatures && (
<SettingsSectionFooter> <SettingsSectionFooter>
<Button <Button
variant="outline" variant="outline"
@@ -292,6 +293,7 @@ export default function CredentialsPage() {
{t("siteRegenerateAndDisconnect")} {t("siteRegenerateAndDisconnect")}
</Button> </Button>
</SettingsSectionFooter> </SettingsSectionFooter>
)}
</SettingsSection> </SettingsSection>
<NewtSiteInstallCommands <NewtSiteInstallCommands
@@ -383,6 +385,7 @@ export default function CredentialsPage() {
</> </>
)} )}
</SettingsSectionBody> </SettingsSectionBody>
{!env.flags.disableEnterpriseFeatures && (
<SettingsSectionFooter> <SettingsSectionFooter>
<Button <Button
onClick={() => setModalOpen(true)} onClick={() => setModalOpen(true)}
@@ -391,6 +394,7 @@ export default function CredentialsPage() {
{t("siteRegenerateAndDisconnect")} {t("siteRegenerateAndDisconnect")}
</Button> </Button>
</SettingsSectionFooter> </SettingsSectionFooter>
)}
</SettingsSection> </SettingsSection>
)} )}
</SettingsContainer> </SettingsContainer>

View File

@@ -121,16 +121,27 @@ export const orgNavSections = (env?: Env): SidebarNavSection[] => [
href: "/{orgId}/settings/access/roles", href: "/{orgId}/settings/access/roles",
icon: <Users className="size-4 flex-none" /> icon: <Users className="size-4 flex-none" />
}, },
// PaidFeaturesAlert
...((build === "oss" && !env?.flags.disableEnterpriseFeatures) ||
build === "saas" ||
env?.flags.useOrgOnlyIdp
? [
{ {
title: "sidebarIdentityProviders", title: "sidebarIdentityProviders",
href: "/{orgId}/settings/idp", href: "/{orgId}/settings/idp",
icon: <Fingerprint className="size-4 flex-none" /> icon: <Fingerprint className="size-4 flex-none" />
}, }
]
: []),
...(!env?.flags.disableEnterpriseFeatures
? [
{ {
title: "sidebarApprovals", title: "sidebarApprovals",
href: "/{orgId}/settings/access/approvals", href: "/{orgId}/settings/access/approvals",
icon: <UserCog className="size-4 flex-none" /> icon: <UserCog className="size-4 flex-none" />
}, }
]
: []),
{ {
title: "sidebarShareableLinks", title: "sidebarShareableLinks",
href: "/{orgId}/settings/share-links", href: "/{orgId}/settings/share-links",
@@ -147,6 +158,8 @@ export const orgNavSections = (env?: Env): SidebarNavSection[] => [
href: "/{orgId}/settings/logs/request", href: "/{orgId}/settings/logs/request",
icon: <SquareMousePointer className="size-4 flex-none" /> icon: <SquareMousePointer className="size-4 flex-none" />
}, },
...(!env?.flags.disableEnterpriseFeatures
? [
{ {
title: "sidebarLogsAccess", title: "sidebarLogsAccess",
href: "/{orgId}/settings/logs/access", href: "/{orgId}/settings/logs/access",
@@ -157,6 +170,8 @@ export const orgNavSections = (env?: Env): SidebarNavSection[] => [
href: "/{orgId}/settings/logs/action", href: "/{orgId}/settings/logs/action",
icon: <Logs className="size-4 flex-none" /> icon: <Logs className="size-4 flex-none" />
} }
]
: [])
]; ];
const analytics = { const analytics = {

View File

@@ -51,6 +51,7 @@ export default function CreateRoleForm({
const { org } = useOrgContext(); const { org } = useOrgContext();
const t = useTranslations(); const t = useTranslations();
const { isPaidUser } = usePaidStatus(); const { isPaidUser } = usePaidStatus();
const { env } = useEnvContext();
const formSchema = z.object({ const formSchema = z.object({
name: z name: z
@@ -161,6 +162,8 @@ export default function CreateRoleForm({
)} )}
/> />
{!env.flags.disableEnterpriseFeatures && (
<>
<PaidFeaturesAlert /> <PaidFeaturesAlert />
<FormField <FormField
@@ -171,7 +174,9 @@ export default function CreateRoleForm({
<FormControl> <FormControl>
<CheckboxWithLabel <CheckboxWithLabel
{...field} {...field}
disabled={!isPaidUser} disabled={
!isPaidUser
}
value="on" value="on"
checked={form.watch( checked={form.watch(
"requireDeviceApproval" "requireDeviceApproval"
@@ -205,6 +210,8 @@ export default function CreateRoleForm({
</FormItem> </FormItem>
)} )}
/> />
</>
)}
</form> </form>
</Form> </Form>
</CredenzaBody> </CredenzaBody>

View File

@@ -59,6 +59,7 @@ export default function EditRoleForm({
const { org } = useOrgContext(); const { org } = useOrgContext();
const t = useTranslations(); const t = useTranslations();
const { isPaidUser } = usePaidStatus(); const { isPaidUser } = usePaidStatus();
const { env } = useEnvContext();
const formSchema = z.object({ const formSchema = z.object({
name: z name: z
@@ -168,6 +169,9 @@ export default function EditRoleForm({
</FormItem> </FormItem>
)} )}
/> />
{!env.flags.disableEnterpriseFeatures && (
<>
<PaidFeaturesAlert /> <PaidFeaturesAlert />
<FormField <FormField
@@ -178,7 +182,9 @@ export default function EditRoleForm({
<FormControl> <FormControl>
<CheckboxWithLabel <CheckboxWithLabel
{...field} {...field}
disabled={!isPaidUser} disabled={
!isPaidUser
}
value="on" value="on"
checked={form.watch( checked={form.watch(
"requireDeviceApproval" "requireDeviceApproval"
@@ -212,6 +218,8 @@ export default function EditRoleForm({
</FormItem> </FormItem>
)} )}
/> />
</>
)}
</form> </form>
</Form> </Form>
</CredenzaBody> </CredenzaBody>

View File

@@ -5,6 +5,7 @@ import { usePaidStatus } from "@app/hooks/usePaidStatus";
import { ExternalLink, KeyRound, Sparkles } from "lucide-react"; import { ExternalLink, KeyRound, Sparkles } from "lucide-react";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import Link from "next/link"; import Link from "next/link";
import { useEnvContext } from "@app/hooks/useEnvContext";
const bannerClassName = const bannerClassName =
"mb-6 border-primary/30 bg-linear-to-br from-primary/10 via-background to-background overflow-hidden"; "mb-6 border-primary/30 bg-linear-to-br from-primary/10 via-background to-background overflow-hidden";
@@ -15,6 +16,12 @@ const bannerRowClassName =
export function PaidFeaturesAlert() { export function PaidFeaturesAlert() {
const t = useTranslations(); const t = useTranslations();
const { hasSaasSubscription, hasEnterpriseLicense } = usePaidStatus(); const { hasSaasSubscription, hasEnterpriseLicense } = usePaidStatus();
const { env } = useEnvContext();
if (env.flags.disableEnterpriseFeatures) {
return null;
}
return ( return (
<> <>
{build === "saas" && !hasSaasSubscription ? ( {build === "saas" && !hasSaasSubscription ? (

View File

@@ -4,7 +4,6 @@ import { createContext } from "react";
type SubscriptionStatusContextType = { type SubscriptionStatusContextType = {
subscriptionStatus: GetOrgSubscriptionResponse | null; subscriptionStatus: GetOrgSubscriptionResponse | null;
updateSubscriptionStatus: (updatedSite: GetOrgSubscriptionResponse) => void; updateSubscriptionStatus: (updatedSite: GetOrgSubscriptionResponse) => void;
isActive: () => boolean;
getTier: () => { tier: string | null; active: boolean }; getTier: () => { tier: string | null; active: boolean };
isSubscribed: () => boolean; isSubscribed: () => boolean;
subscribed: boolean; subscribed: boolean;

View File

@@ -8,14 +8,29 @@ export function usePaidStatus() {
// Check if features are disabled due to licensing/subscription // Check if features are disabled due to licensing/subscription
const hasEnterpriseLicense = build === "enterprise" && isUnlocked(); const hasEnterpriseLicense = build === "enterprise" && isUnlocked();
const hasSaasSubscription = const tierData = subscription?.getTier();
build === "saas" && const hasSaasSubscription = build === "saas" && tierData?.active;
subscription?.isSubscribed() &&
subscription.isActive(); function isPaidUser(tiers: string[]): boolean {
if (hasEnterpriseLicense) {
return true;
}
if (
hasSaasSubscription &&
tierData?.tier &&
tiers.includes(tierData.tier)
) {
return true;
}
return false;
}
return { return {
hasEnterpriseLicense, hasEnterpriseLicense,
hasSaasSubscription, hasSaasSubscription,
isPaidUser: hasEnterpriseLicense || hasSaasSubscription isPaidUser,
subscriptionTier: tierData?.tier
}; };
} }

View File

@@ -65,7 +65,11 @@ export function pullEnv(): Env {
? true ? true
: false, : false,
useOrgOnlyIdp: useOrgOnlyIdp:
process.env.USE_ORG_ONLY_IDP === "true" ? true : false process.env.USE_ORG_ONLY_IDP === "true" ? true : false,
disableEnterpriseFeatures:
process.env.DISABLE_ENTERPRISE_FEATURES === "true"
? true
: false
}, },
branding: { branding: {

View File

@@ -35,6 +35,7 @@ export type Env = {
usePangolinDns: boolean; usePangolinDns: boolean;
disableProductHelpBanners: boolean; disableProductHelpBanners: boolean;
useOrgOnlyIdp: boolean; useOrgOnlyIdp: boolean;
disableEnterpriseFeatures: boolean;
}; };
branding: { branding: {
appName?: string; appName?: string;

View File

@@ -31,16 +31,6 @@ export function SubscriptionStatusProvider({
}); });
}; };
const isActive = () => {
if (subscriptionStatus?.subscriptions) {
// Check if any subscription is active
return subscriptionStatus.subscriptions.some(
(sub) => sub.subscription?.status === "active"
);
}
return false;
};
const getTier = () => { const getTier = () => {
if (subscriptionStatus?.subscriptions) { if (subscriptionStatus?.subscriptions) {
// Iterate through all subscriptions // Iterate through all subscriptions
@@ -65,9 +55,6 @@ export function SubscriptionStatusProvider({
}; };
const isSubscribed = () => { const isSubscribed = () => {
if (build === "enterprise") {
return true;
}
const { tier, active } = getTier(); const { tier, active } = getTier();
return ( return (
(tier == "tier1" || tier == "tier2" || tier == "tier3") && (tier == "tier1" || tier == "tier2" || tier == "tier3") &&
@@ -82,7 +69,6 @@ export function SubscriptionStatusProvider({
value={{ value={{
subscriptionStatus: subscriptionStatusState, subscriptionStatus: subscriptionStatusState,
updateSubscriptionStatus, updateSubscriptionStatus,
isActive,
getTier, getTier,
isSubscribed, isSubscribed,
subscribed subscribed