From 2810632f4afa25d02e5057d123b8a0694436b176 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Wed, 7 Jan 2026 20:40:59 -0800 Subject: [PATCH] add flag to enable org only idp in ee --- server/private/lib/config.ts | 4 ++ server/private/lib/readConfigFile.ts | 3 +- .../loginPage/upsertLoginPageBranding.ts | 7 +++- .../[orgId]/settings/(private)/idp/layout.tsx | 18 ++++++++ src/app/[orgId]/settings/layout.tsx | 2 +- .../proxy/[niceId]/authentication/page.tsx | 9 ++-- src/app/admin/layout.tsx | 5 ++- src/app/auth/login/page.tsx | 4 +- src/app/auth/org/[orgId]/page.tsx | 41 +++++++++---------- src/app/auth/org/page.tsx | 6 +-- src/app/auth/resource/[resourceGuid]/page.tsx | 2 +- src/app/navigation.tsx | 27 ++++++------ src/components/AuthPageBrandingForm.tsx | 6 ++- src/lib/pullEnv.ts | 4 +- src/lib/queries.ts | 15 ++++++- src/lib/types/env.ts | 1 + 16 files changed, 101 insertions(+), 53 deletions(-) create mode 100644 src/app/[orgId]/settings/(private)/idp/layout.tsx diff --git a/server/private/lib/config.ts b/server/private/lib/config.ts index 97baf1e0..ae9ca5c7 100644 --- a/server/private/lib/config.ts +++ b/server/private/lib/config.ts @@ -139,6 +139,10 @@ export class PrivateConfig { process.env.USE_PANGOLIN_DNS = this.rawPrivateConfig.flags.use_pangolin_dns.toString(); } + if (this.rawPrivateConfig.flags.use_org_only_idp) { + process.env.USE_ORG_ONLY_IDP = + this.rawPrivateConfig.flags.use_org_only_idp.toString(); + } } public getRawPrivateConfig() { diff --git a/server/private/lib/readConfigFile.ts b/server/private/lib/readConfigFile.ts index c986e62d..374dee7c 100644 --- a/server/private/lib/readConfigFile.ts +++ b/server/private/lib/readConfigFile.ts @@ -83,7 +83,8 @@ export const privateConfigSchema = z.object({ flags: z .object({ enable_redis: z.boolean().optional().default(false), - use_pangolin_dns: z.boolean().optional().default(false) + use_pangolin_dns: z.boolean().optional().default(false), + use_org_only_idp: z.boolean().optional().default(false) }) .optional() .prefault({}), diff --git a/server/private/routers/loginPage/upsertLoginPageBranding.ts b/server/private/routers/loginPage/upsertLoginPageBranding.ts index f9f9d08c..4e2b666b 100644 --- a/server/private/routers/loginPage/upsertLoginPageBranding.ts +++ b/server/private/routers/loginPage/upsertLoginPageBranding.ts @@ -28,6 +28,7 @@ import { eq, InferInsertModel } from "drizzle-orm"; import { getOrgTierData } from "#private/lib/billing"; import { TierId } from "@server/lib/billing/tiers"; import { build } from "@server/build"; +import config from "@server/private/lib/config"; const paramsSchema = z.strictObject({ orgId: z.string() @@ -94,8 +95,10 @@ export async function upsertLoginPageBranding( typeof loginPageBranding >; - if (build !== "saas") { - // org branding settings are only considered in the saas build + if ( + build !== "saas" && + !config.getRawPrivateConfig().flags.use_org_only_idp + ) { const { orgTitle, orgSubtitle, ...rest } = updateData; updateData = rest; } diff --git a/src/app/[orgId]/settings/(private)/idp/layout.tsx b/src/app/[orgId]/settings/(private)/idp/layout.tsx new file mode 100644 index 00000000..dcb73afb --- /dev/null +++ b/src/app/[orgId]/settings/(private)/idp/layout.tsx @@ -0,0 +1,18 @@ +import { pullEnv } from "@app/lib/pullEnv"; +import { build } from "@server/build"; +import { redirect } from "next/navigation"; + +interface LayoutProps { + children: React.ReactNode; + params: Promise<{}>; +} + +export default async function Layout(props: LayoutProps) { + const env = pullEnv(); + + if (build !== "saas" && !env.flags.useOrgOnlyIdp) { + redirect("/"); + } + + return props.children; +} diff --git a/src/app/[orgId]/settings/layout.tsx b/src/app/[orgId]/settings/layout.tsx index 8f44c23c..34ed3ac2 100644 --- a/src/app/[orgId]/settings/layout.tsx +++ b/src/app/[orgId]/settings/layout.tsx @@ -82,7 +82,7 @@ export default async function SettingsLayout(props: SettingsLayoutProps) { {children} diff --git a/src/app/[orgId]/settings/resources/proxy/[niceId]/authentication/page.tsx b/src/app/[orgId]/settings/resources/proxy/[niceId]/authentication/page.tsx index 968b2700..f021076f 100644 --- a/src/app/[orgId]/settings/resources/proxy/[niceId]/authentication/page.tsx +++ b/src/app/[orgId]/settings/resources/proxy/[niceId]/authentication/page.tsx @@ -36,8 +36,8 @@ import { import type { ResourceContextType } from "@app/contexts/resourceContext"; import { useEnvContext } from "@app/hooks/useEnvContext"; import { useOrgContext } from "@app/hooks/useOrgContext"; +import { usePaidStatus } from "@app/hooks/usePaidStatus"; import { useResourceContext } from "@app/hooks/useResourceContext"; -import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext"; import { toast } from "@app/hooks/useToast"; import { createApiClient, formatAxiosError } from "@app/lib/api"; import { orgQueries, resourceQueries } from "@app/lib/queries"; @@ -95,7 +95,7 @@ export default function ResourceAuthenticationPage() { const router = useRouter(); const t = useTranslations(); - const subscription = useSubscriptionStatusContext(); + const { isPaidUser } = usePaidStatus(); const queryClient = useQueryClient(); const { data: resourceRoles = [], isLoading: isLoadingResourceRoles } = @@ -129,7 +129,8 @@ export default function ResourceAuthenticationPage() { ); const { data: orgIdps = [], isLoading: isLoadingOrgIdps } = useQuery( orgQueries.identityProviders({ - orgId: org.org.orgId + orgId: org.org.orgId, + useOrgOnlyIdp: env.flags.useOrgOnlyIdp }) ); @@ -159,7 +160,7 @@ export default function ResourceAuthenticationPage() { const allIdps = useMemo(() => { if (build === "saas") { - if (subscription?.subscribed) { + if (isPaidUser) { return orgIdps.map((idp) => ({ id: idp.idpId, text: idp.name diff --git a/src/app/admin/layout.tsx b/src/app/admin/layout.tsx index 060f18ac..44d85b99 100644 --- a/src/app/admin/layout.tsx +++ b/src/app/admin/layout.tsx @@ -11,6 +11,7 @@ import { AxiosResponse } from "axios"; import { authCookieHeader } from "@app/lib/api/cookies"; import { Layout } from "@app/components/Layout"; import { adminNavSections } from "../navigation"; +import { pullEnv } from "@app/lib/pullEnv"; export const dynamic = "force-dynamic"; @@ -27,6 +28,8 @@ export default async function AdminLayout(props: LayoutProps) { const getUser = cache(verifySession); const user = await getUser(); + const env = pullEnv(); + if (!user || !user.serverAdmin) { redirect(`/`); } @@ -48,7 +51,7 @@ export default async function AdminLayout(props: LayoutProps) { return ( - + {props.children} diff --git a/src/app/auth/login/page.tsx b/src/app/auth/login/page.tsx index bd6327fd..fb327af5 100644 --- a/src/app/auth/login/page.tsx +++ b/src/app/auth/login/page.tsx @@ -70,7 +70,7 @@ export default async function Page(props: { } let loginIdps: LoginFormIDP[] = []; - if (build !== "saas") { + if (build === "oss" || !env.flags.useOrgOnlyIdp) { const idpsRes = await cache( async () => await priv.get>("/idp") )(); @@ -121,7 +121,7 @@ export default async function Page(props: {

)} - {!isInvite && build === "saas" ? ( + {!isInvite && (build === "saas" || env.flags.useOrgOnlyIdp) ? (
{t("needToSignInToOrg")} >( - `/org/${orgId}/idp` - ); + const idpsRes = await priv.get>( + `/org/${orgId}/idp` + ); - loginIdps = idpsRes.data.data.idps.map((idp) => ({ - idpId: idp.idpId, - name: idp.name, - variant: idp.variant - })) as LoginFormIDP[]; - } + loginIdps = idpsRes.data.data.idps.map((idp) => ({ + idpId: idp.idpId, + name: idp.name, + variant: idp.variant + })) as LoginFormIDP[]; let branding: LoadLoginPageBrandingResponse | null = null; - if (build === "saas") { - try { - const res = await priv.get< - AxiosResponse - >(`/login-page-branding?orgId=${orgId}`); - if (res.status === 200) { - branding = res.data.data; - } - } catch (error) {} - } + try { + const res = await priv.get< + AxiosResponse + >(`/login-page-branding?orgId=${orgId}`); + if (res.status === 200) { + branding = res.data.data; + } + } catch (error) {} return ( diff --git a/src/app/navigation.tsx b/src/app/navigation.tsx index 54576c0c..345b5a4c 100644 --- a/src/app/navigation.tsx +++ b/src/app/navigation.tsx @@ -1,4 +1,5 @@ import { SidebarNavItem } from "@app/components/SidebarNav"; +import { Env } from "@app/lib/types/env"; import { build } from "@server/build"; import { Settings, @@ -39,7 +40,7 @@ export const orgLangingNavItems: SidebarNavItem[] = [ } ]; -export const orgNavSections = (): SidebarNavSection[] => [ +export const orgNavSections = (env?: Env): SidebarNavSection[] => [ { heading: "sidebarGeneral", items: [ @@ -92,8 +93,7 @@ export const orgNavSections = (): SidebarNavSection[] => [ { title: "sidebarRemoteExitNodes", href: "/{orgId}/settings/remote-exit-nodes", - icon: , - showEE: true + icon: } ] : []) @@ -123,13 +123,12 @@ export const orgNavSections = (): SidebarNavSection[] => [ href: "/{orgId}/settings/access/roles", icon: }, - ...(build == "saas" + ...(build == "saas" || env?.flags.useOrgOnlyIdp ? [ { title: "sidebarIdentityProviders", href: "/{orgId}/settings/idp", - icon: , - showEE: true + icon: } ] : []), @@ -228,7 +227,7 @@ export const orgNavSections = (): SidebarNavSection[] => [ } ]; -export const adminNavSections: SidebarNavSection[] = [ +export const adminNavSections = (env?: Env): SidebarNavSection[] => [ { heading: "sidebarAdmin", items: [ @@ -242,11 +241,15 @@ export const adminNavSections: SidebarNavSection[] = [ href: "/admin/api-keys", icon: }, - { - title: "sidebarIdentityProviders", - href: "/admin/idp", - icon: - }, + ...(build === "oss" || !env?.flags.useOrgOnlyIdp + ? [ + { + title: "sidebarIdentityProviders", + href: "/admin/idp", + icon: + } + ] + : []), ...(build == "enterprise" ? [ { diff --git a/src/components/AuthPageBrandingForm.tsx b/src/components/AuthPageBrandingForm.tsx index 67e2232f..119a39cb 100644 --- a/src/components/AuthPageBrandingForm.tsx +++ b/src/components/AuthPageBrandingForm.tsx @@ -118,6 +118,7 @@ export default function AuthPageBrandingForm({ const brandingData = form.getValues(); if (!isValid || !isPaidUser) return; + try { const updateRes = await api.put( `/org/${orgId}/login-page-branding`, @@ -289,7 +290,8 @@ export default function AuthPageBrandingForm({
- {build === "saas" && ( + {build === "saas" || + env.env.flags.useOrgOnlyIdp ? ( <>
@@ -343,7 +345,7 @@ export default function AuthPageBrandingForm({ />
- )} + ) : null}
diff --git a/src/lib/pullEnv.ts b/src/lib/pullEnv.ts index ed0057de..319e08f0 100644 --- a/src/lib/pullEnv.ts +++ b/src/lib/pullEnv.ts @@ -63,7 +63,9 @@ export function pullEnv(): Env { disableProductHelpBanners: process.env.FLAGS_DISABLE_PRODUCT_HELP_BANNERS === "true" ? true - : false + : false, + useOrgOnlyIdp: + process.env.USE_ORG_ONLY_IDP === "true" ? true : false }, branding: { diff --git a/src/lib/queries.ts b/src/lib/queries.ts index 5ea3c2f2..2034bba8 100644 --- a/src/lib/queries.ts +++ b/src/lib/queries.ts @@ -157,7 +157,13 @@ export const orgQueries = { return res.data.data.domains; } }), - identityProviders: ({ orgId }: { orgId: string }) => + identityProviders: ({ + orgId, + useOrgOnlyIdp + }: { + orgId: string; + useOrgOnlyIdp?: boolean; + }) => queryOptions({ queryKey: ["ORG", orgId, "IDPS"] as const, queryFn: async ({ signal, meta }) => { @@ -165,7 +171,12 @@ export const orgQueries = { AxiosResponse<{ idps: { idpId: number; name: string }[]; }> - >(build === "saas" ? `/org/${orgId}/idp` : "/idp", { signal }); + >( + build === "saas" || useOrgOnlyIdp + ? `/org/${orgId}/idp` + : "/idp", + { signal } + ); return res.data.data.idps; } }) diff --git a/src/lib/types/env.ts b/src/lib/types/env.ts index 1f54f680..f99e1994 100644 --- a/src/lib/types/env.ts +++ b/src/lib/types/env.ts @@ -34,6 +34,7 @@ export type Env = { hideSupporterKey: boolean; usePangolinDns: boolean; disableProductHelpBanners: boolean; + useOrgOnlyIdp: boolean; }; branding: { appName?: string;