diff --git a/server/lib/billing/tierMatrix.ts b/server/lib/billing/tierMatrix.ts index bc5cb950..e878f8fe 100644 --- a/server/lib/billing/tierMatrix.ts +++ b/server/lib/billing/tierMatrix.ts @@ -13,17 +13,13 @@ export enum TierFeature { DevicePosture = "devicePosture", TwoFactorEnforcement = "twoFactorEnforcement", SessionDurationPolicies = "sessionDurationPolicies", - PasswordExpirationPolicies = "passwordExpirationPolicies" + PasswordExpirationPolicies = "passwordExpirationPolicies", + AutoProvisioning = "autoProvisioning" } export const tierMatrix: Record = { [TierFeature.OrgOidc]: ["tier1", "tier2", "tier3", "enterprise"], - [TierFeature.LoginPageDomain]: [ - "tier1", - "tier2", - "tier3", - "enterprise" - ], + [TierFeature.LoginPageDomain]: ["tier1", "tier2", "tier3", "enterprise"], [TierFeature.DeviceApprovals]: ["tier1", "tier3", "enterprise"], [TierFeature.LoginPageBranding]: ["tier1", "tier3", "enterprise"], [TierFeature.LogExport]: ["tier3", "enterprise"], @@ -32,7 +28,23 @@ export const tierMatrix: Record = { [TierFeature.RotateCredentials]: ["tier1", "tier2", "tier3", "enterprise"], [TierFeature.MaintencePage]: ["tier1", "tier2", "tier3", "enterprise"], [TierFeature.DevicePosture]: ["tier2", "tier3", "enterprise"], - [TierFeature.TwoFactorEnforcement]: ["tier1", "tier2", "tier3", "enterprise"], - [TierFeature.SessionDurationPolicies]: ["tier1", "tier2", "tier3", "enterprise"], - [TierFeature.PasswordExpirationPolicies]: ["tier1", "tier2", "tier3", "enterprise"] + [TierFeature.TwoFactorEnforcement]: [ + "tier1", + "tier2", + "tier3", + "enterprise" + ], + [TierFeature.SessionDurationPolicies]: [ + "tier1", + "tier2", + "tier3", + "enterprise" + ], + [TierFeature.PasswordExpirationPolicies]: [ + "tier1", + "tier2", + "tier3", + "enterprise" + ], + [TierFeature.AutoProvisioning]: ["tier1", "tier3", "enterprise"] }; diff --git a/server/private/routers/orgIdp/createOrgOidcIdp.ts b/server/private/routers/orgIdp/createOrgOidcIdp.ts index bee04340..23fa4b7c 100644 --- a/server/private/routers/orgIdp/createOrgOidcIdp.ts +++ b/server/private/routers/orgIdp/createOrgOidcIdp.ts @@ -25,6 +25,8 @@ import { generateOidcRedirectUrl } from "@server/lib/idp/generateRedirectUrl"; import { encrypt } from "@server/lib/crypto"; import config from "@server/lib/config"; import { CreateOrgIdpResponse } from "@server/routers/orgIdp/types"; +import { isSubscribed } from "#dynamic/lib/isSubscribed"; +import { tierMatrix } from "@server/lib/billing/tierMatrix"; const paramsSchema = z.strictObject({ orgId: z.string().nonempty() }); @@ -100,12 +102,21 @@ export async function createOrgOidcIdp( emailPath, namePath, name, - autoProvision, variant, roleMapping, tags } = parsedBody.data; + let { autoProvision } = parsedBody.data; + + const subscribed = await isSubscribed( + orgId, + tierMatrix.deviceApprovals + ); + if (!subscribed) { + autoProvision = false; + } + const key = config.getRawConfig().server.secret!; const encryptedSecret = encrypt(clientSecret, key); diff --git a/server/private/routers/orgIdp/updateOrgOidcIdp.ts b/server/private/routers/orgIdp/updateOrgOidcIdp.ts index e01bdba0..d83ff569 100644 --- a/server/private/routers/orgIdp/updateOrgOidcIdp.ts +++ b/server/private/routers/orgIdp/updateOrgOidcIdp.ts @@ -24,6 +24,8 @@ import { idp, idpOidcConfig } from "@server/db"; import { eq, and } from "drizzle-orm"; import { encrypt } from "@server/lib/crypto"; import config from "@server/lib/config"; +import { isSubscribed } from "#dynamic/lib/isSubscribed"; +import { tierMatrix } from "@server/lib/billing/tierMatrix"; const paramsSchema = z .object({ @@ -106,11 +108,20 @@ export async function updateOrgOidcIdp( emailPath, namePath, name, - autoProvision, roleMapping, tags } = parsedBody.data; + let { autoProvision } = parsedBody.data; + + const subscribed = await isSubscribed( + orgId, + tierMatrix.deviceApprovals + ); + if (!subscribed) { + autoProvision = false; + } + // Check if IDP exists and is of type OIDC const [existingIdp] = await db .select() diff --git a/server/routers/idp/validateOidcCallback.ts b/server/routers/idp/validateOidcCallback.ts index 22c49f42..7831e560 100644 --- a/server/routers/idp/validateOidcCallback.ts +++ b/server/routers/idp/validateOidcCallback.ts @@ -34,6 +34,8 @@ import { FeatureId } from "@server/lib/billing"; import { usageService } from "@server/lib/billing/usageService"; import { build } from "@server/build"; import { calculateUserClientsForOrgs } from "@server/lib/calculateUserClientsForOrgs"; +import { isSubscribed } from "#dynamic/lib/isSubscribed"; +import { tierMatrix } from "@server/lib/billing/tierMatrix"; const ensureTrailingSlash = (url: string): string => { return url; @@ -326,6 +328,33 @@ export async function validateOidcCallback( .where(eq(idpOrg.idpId, existingIdp.idp.idpId)) .innerJoin(orgs, eq(orgs.orgId, idpOrg.orgId)); allOrgs = idpOrgs.map((o) => o.orgs); + + // TODO: when there are multiple orgs we need to do this better!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1 + if (allOrgs.length > 1) { + // for some reason there is more than one org + logger.error( + "More than one organization linked to this IdP. This should not happen with auto-provisioning enabled." + ); + return next( + createHttpError( + HttpCode.INTERNAL_SERVER_ERROR, + "Multiple organizations linked to this IdP. Please contact support." + ) + ); + } + + const subscribed = await isSubscribed( + allOrgs[0].orgId, + tierMatrix.autoProvisioning + ); + if (subscribed) { + return next( + createHttpError( + HttpCode.FORBIDDEN, + "This organization's current plan does not support this feature." + ) + ); + } } else { allOrgs = await db.select().from(orgs); }