add flag to enable org only idp in ee

This commit is contained in:
miloschwartz
2026-01-07 20:40:59 -08:00
parent 2ca400ab16
commit 2810632f4a
16 changed files with 101 additions and 53 deletions

View File

@@ -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;
}

View File

@@ -82,7 +82,7 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
<Layout
orgId={params.orgId}
orgs={orgs}
navItems={orgNavSections()}
navItems={orgNavSections(env)}
>
{children}
</Layout>

View File

@@ -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

View File

@@ -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 (
<UserProvider user={user}>
<Layout orgs={orgs} navItems={adminNavSections}>
<Layout orgs={orgs} navItems={adminNavSections(env)}>
{props.children}
</Layout>
</UserProvider>

View File

@@ -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<AxiosResponse<ListIdpsResponse>>("/idp")
)();
@@ -121,7 +121,7 @@ export default async function Page(props: {
</p>
)}
{!isInvite && build === "saas" ? (
{!isInvite && (build === "saas" || env.flags.useOrgOnlyIdp) ? (
<div className="text-center text-muted-foreground mt-12 flex flex-col items-center">
<span>{t("needToSignInToOrg")}</span>
<Link

View File

@@ -11,6 +11,7 @@ import {
} from "@server/routers/loginPage/types";
import { redirect } from "next/navigation";
import OrgLoginPage from "@app/components/OrgLoginPage";
import { pullEnv } from "@app/lib/pullEnv";
export const dynamic = "force-dynamic";
@@ -21,7 +22,9 @@ export default async function OrgAuthPage(props: {
const searchParams = await props.searchParams;
const params = await props.params;
if (build !== "saas") {
const env = pullEnv();
if (build !== "saas" && !env.flags.useOrgOnlyIdp) {
const queryString = new URLSearchParams(searchParams as any).toString();
redirect(`/auth/login${queryString ? `?${queryString}` : ""}`);
}
@@ -50,29 +53,25 @@ export default async function OrgAuthPage(props: {
} catch (e) {}
let loginIdps: LoginFormIDP[] = [];
if (build === "saas") {
const idpsRes = await priv.get<AxiosResponse<ListOrgIdpsResponse>>(
`/org/${orgId}/idp`
);
const idpsRes = await priv.get<AxiosResponse<ListOrgIdpsResponse>>(
`/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<LoadLoginPageBrandingResponse>
>(`/login-page-branding?orgId=${orgId}`);
if (res.status === 200) {
branding = res.data.data;
}
} catch (error) {}
}
try {
const res = await priv.get<
AxiosResponse<LoadLoginPageBrandingResponse>
>(`/login-page-branding?orgId=${orgId}`);
if (res.status === 200) {
branding = res.data.data;
}
} catch (error) {}
return (
<OrgLoginPage

View File

@@ -33,12 +33,12 @@ export default async function OrgAuthPage(props: {
const forceLoginParam = searchParams.forceLogin;
const forceLogin = forceLoginParam === "true";
if (build !== "saas") {
const env = pullEnv();
if (build !== "saas" && !env.flags.useOrgOnlyIdp) {
redirect("/");
}
const env = pullEnv();
const authHeader = await authCookieHeader();
if (searchParams.token) {

View File

@@ -204,7 +204,7 @@ export default async function ResourceAuthPage(props: {
}
let loginIdps: LoginFormIDP[] = [];
if (build === "saas") {
if (build === "saas" || env.flags.useOrgOnlyIdp) {
if (subscribed) {
const idpsRes = await cache(
async () =>

View File

@@ -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: <Server className="size-4 flex-none" />,
showEE: true
icon: <Server className="size-4 flex-none" />
}
]
: [])
@@ -123,13 +123,12 @@ export const orgNavSections = (): SidebarNavSection[] => [
href: "/{orgId}/settings/access/roles",
icon: <Users className="size-4 flex-none" />
},
...(build == "saas"
...(build == "saas" || env?.flags.useOrgOnlyIdp
? [
{
title: "sidebarIdentityProviders",
href: "/{orgId}/settings/idp",
icon: <Fingerprint className="size-4 flex-none" />,
showEE: true
icon: <Fingerprint className="size-4 flex-none" />
}
]
: []),
@@ -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: <KeyRound className="size-4 flex-none" />
},
{
title: "sidebarIdentityProviders",
href: "/admin/idp",
icon: <Fingerprint className="size-4 flex-none" />
},
...(build === "oss" || !env?.flags.useOrgOnlyIdp
? [
{
title: "sidebarIdentityProviders",
href: "/admin/idp",
icon: <Fingerprint className="size-4 flex-none" />
}
]
: []),
...(build == "enterprise"
? [
{

View File

@@ -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({
</div>
</div>
{build === "saas" && (
{build === "saas" ||
env.env.flags.useOrgOnlyIdp ? (
<>
<div className="mt-3 mb-6">
<SettingsSectionTitle>
@@ -343,7 +345,7 @@ export default function AuthPageBrandingForm({
/>
</div>
</>
)}
) : null}
<div className="mt-3 mb-6">
<SettingsSectionTitle>

View File

@@ -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: {

View File

@@ -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;
}
})

View File

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