mirror of
https://github.com/fosrl/pangolin.git
synced 2026-01-28 22:00:51 +00:00
Merge branch 'org-only-idp' into dev
This commit is contained in:
@@ -139,6 +139,10 @@ export class PrivateConfig {
|
|||||||
process.env.USE_PANGOLIN_DNS =
|
process.env.USE_PANGOLIN_DNS =
|
||||||
this.rawPrivateConfig.flags.use_pangolin_dns.toString();
|
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() {
|
public getRawPrivateConfig() {
|
||||||
|
|||||||
@@ -83,7 +83,8 @@ export const privateConfigSchema = z.object({
|
|||||||
flags: z
|
flags: z
|
||||||
.object({
|
.object({
|
||||||
enable_redis: z.boolean().optional().default(false),
|
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()
|
.optional()
|
||||||
.prefault({}),
|
.prefault({}),
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import { eq, InferInsertModel } from "drizzle-orm";
|
|||||||
import { getOrgTierData } from "#private/lib/billing";
|
import { getOrgTierData } from "#private/lib/billing";
|
||||||
import { TierId } from "@server/lib/billing/tiers";
|
import { TierId } from "@server/lib/billing/tiers";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
|
import config from "@server/private/lib/config";
|
||||||
|
|
||||||
const paramsSchema = z.strictObject({
|
const paramsSchema = z.strictObject({
|
||||||
orgId: z.string()
|
orgId: z.string()
|
||||||
@@ -94,8 +95,10 @@ export async function upsertLoginPageBranding(
|
|||||||
typeof loginPageBranding
|
typeof loginPageBranding
|
||||||
>;
|
>;
|
||||||
|
|
||||||
if (build !== "saas") {
|
if (
|
||||||
// org branding settings are only considered in the saas build
|
build !== "saas" &&
|
||||||
|
!config.getRawPrivateConfig().flags.use_org_only_idp
|
||||||
|
) {
|
||||||
const { orgTitle, orgSubtitle, ...rest } = updateData;
|
const { orgTitle, orgSubtitle, ...rest } = updateData;
|
||||||
updateData = rest;
|
updateData = rest;
|
||||||
}
|
}
|
||||||
|
|||||||
18
src/app/[orgId]/settings/(private)/idp/layout.tsx
Normal file
18
src/app/[orgId]/settings/(private)/idp/layout.tsx
Normal 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;
|
||||||
|
}
|
||||||
@@ -82,7 +82,7 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
|
|||||||
<Layout
|
<Layout
|
||||||
orgId={params.orgId}
|
orgId={params.orgId}
|
||||||
orgs={orgs}
|
orgs={orgs}
|
||||||
navItems={orgNavSections()}
|
navItems={orgNavSections(env)}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ import {
|
|||||||
import type { ResourceContextType } from "@app/contexts/resourceContext";
|
import type { ResourceContextType } from "@app/contexts/resourceContext";
|
||||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||||
import { useOrgContext } from "@app/hooks/useOrgContext";
|
import { useOrgContext } from "@app/hooks/useOrgContext";
|
||||||
|
import { usePaidStatus } from "@app/hooks/usePaidStatus";
|
||||||
import { useResourceContext } from "@app/hooks/useResourceContext";
|
import { useResourceContext } from "@app/hooks/useResourceContext";
|
||||||
import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext";
|
|
||||||
import { toast } from "@app/hooks/useToast";
|
import { toast } from "@app/hooks/useToast";
|
||||||
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
||||||
import { orgQueries, resourceQueries } from "@app/lib/queries";
|
import { orgQueries, resourceQueries } from "@app/lib/queries";
|
||||||
@@ -95,7 +95,7 @@ export default function ResourceAuthenticationPage() {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
|
|
||||||
const subscription = useSubscriptionStatusContext();
|
const { isPaidUser } = usePaidStatus();
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { data: resourceRoles = [], isLoading: isLoadingResourceRoles } =
|
const { data: resourceRoles = [], isLoading: isLoadingResourceRoles } =
|
||||||
@@ -129,7 +129,8 @@ export default function ResourceAuthenticationPage() {
|
|||||||
);
|
);
|
||||||
const { data: orgIdps = [], isLoading: isLoadingOrgIdps } = useQuery(
|
const { data: orgIdps = [], isLoading: isLoadingOrgIdps } = useQuery(
|
||||||
orgQueries.identityProviders({
|
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(() => {
|
const allIdps = useMemo(() => {
|
||||||
if (build === "saas") {
|
if (build === "saas") {
|
||||||
if (subscription?.subscribed) {
|
if (isPaidUser) {
|
||||||
return orgIdps.map((idp) => ({
|
return orgIdps.map((idp) => ({
|
||||||
id: idp.idpId,
|
id: idp.idpId,
|
||||||
text: idp.name
|
text: idp.name
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { AxiosResponse } from "axios";
|
|||||||
import { authCookieHeader } from "@app/lib/api/cookies";
|
import { authCookieHeader } from "@app/lib/api/cookies";
|
||||||
import { Layout } from "@app/components/Layout";
|
import { Layout } from "@app/components/Layout";
|
||||||
import { adminNavSections } from "../navigation";
|
import { adminNavSections } from "../navigation";
|
||||||
|
import { pullEnv } from "@app/lib/pullEnv";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
@@ -27,6 +28,8 @@ export default async function AdminLayout(props: LayoutProps) {
|
|||||||
const getUser = cache(verifySession);
|
const getUser = cache(verifySession);
|
||||||
const user = await getUser();
|
const user = await getUser();
|
||||||
|
|
||||||
|
const env = pullEnv();
|
||||||
|
|
||||||
if (!user || !user.serverAdmin) {
|
if (!user || !user.serverAdmin) {
|
||||||
redirect(`/`);
|
redirect(`/`);
|
||||||
}
|
}
|
||||||
@@ -48,7 +51,7 @@ export default async function AdminLayout(props: LayoutProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<UserProvider user={user}>
|
<UserProvider user={user}>
|
||||||
<Layout orgs={orgs} navItems={adminNavSections}>
|
<Layout orgs={orgs} navItems={adminNavSections(env)}>
|
||||||
{props.children}
|
{props.children}
|
||||||
</Layout>
|
</Layout>
|
||||||
</UserProvider>
|
</UserProvider>
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export default async function Page(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let loginIdps: LoginFormIDP[] = [];
|
let loginIdps: LoginFormIDP[] = [];
|
||||||
if (build !== "saas") {
|
if (build === "oss" || !env.flags.useOrgOnlyIdp) {
|
||||||
const idpsRes = await cache(
|
const idpsRes = await cache(
|
||||||
async () => await priv.get<AxiosResponse<ListIdpsResponse>>("/idp")
|
async () => await priv.get<AxiosResponse<ListIdpsResponse>>("/idp")
|
||||||
)();
|
)();
|
||||||
@@ -121,7 +121,7 @@ export default async function Page(props: {
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isInvite && build === "saas" ? (
|
{!isInvite && (build === "saas" || env.flags.useOrgOnlyIdp) ? (
|
||||||
<div className="text-center text-muted-foreground mt-12 flex flex-col items-center">
|
<div className="text-center text-muted-foreground mt-12 flex flex-col items-center">
|
||||||
<span>{t("needToSignInToOrg")}</span>
|
<span>{t("needToSignInToOrg")}</span>
|
||||||
<Link
|
<Link
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
} from "@server/routers/loginPage/types";
|
} from "@server/routers/loginPage/types";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import OrgLoginPage from "@app/components/OrgLoginPage";
|
import OrgLoginPage from "@app/components/OrgLoginPage";
|
||||||
|
import { pullEnv } from "@app/lib/pullEnv";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
@@ -21,7 +22,9 @@ export default async function OrgAuthPage(props: {
|
|||||||
const searchParams = await props.searchParams;
|
const searchParams = await props.searchParams;
|
||||||
const params = await props.params;
|
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();
|
const queryString = new URLSearchParams(searchParams as any).toString();
|
||||||
redirect(`/auth/login${queryString ? `?${queryString}` : ""}`);
|
redirect(`/auth/login${queryString ? `?${queryString}` : ""}`);
|
||||||
}
|
}
|
||||||
@@ -50,29 +53,25 @@ export default async function OrgAuthPage(props: {
|
|||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
let loginIdps: LoginFormIDP[] = [];
|
let loginIdps: LoginFormIDP[] = [];
|
||||||
if (build === "saas") {
|
const idpsRes = await priv.get<AxiosResponse<ListOrgIdpsResponse>>(
|
||||||
const idpsRes = await priv.get<AxiosResponse<ListOrgIdpsResponse>>(
|
`/org/${orgId}/idp`
|
||||||
`/org/${orgId}/idp`
|
);
|
||||||
);
|
|
||||||
|
|
||||||
loginIdps = idpsRes.data.data.idps.map((idp) => ({
|
loginIdps = idpsRes.data.data.idps.map((idp) => ({
|
||||||
idpId: idp.idpId,
|
idpId: idp.idpId,
|
||||||
name: idp.name,
|
name: idp.name,
|
||||||
variant: idp.variant
|
variant: idp.variant
|
||||||
})) as LoginFormIDP[];
|
})) as LoginFormIDP[];
|
||||||
}
|
|
||||||
|
|
||||||
let branding: LoadLoginPageBrandingResponse | null = null;
|
let branding: LoadLoginPageBrandingResponse | null = null;
|
||||||
if (build === "saas") {
|
try {
|
||||||
try {
|
const res = await priv.get<
|
||||||
const res = await priv.get<
|
AxiosResponse<LoadLoginPageBrandingResponse>
|
||||||
AxiosResponse<LoadLoginPageBrandingResponse>
|
>(`/login-page-branding?orgId=${orgId}`);
|
||||||
>(`/login-page-branding?orgId=${orgId}`);
|
if (res.status === 200) {
|
||||||
if (res.status === 200) {
|
branding = res.data.data;
|
||||||
branding = res.data.data;
|
}
|
||||||
}
|
} catch (error) {}
|
||||||
} catch (error) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OrgLoginPage
|
<OrgLoginPage
|
||||||
|
|||||||
@@ -33,12 +33,12 @@ export default async function OrgAuthPage(props: {
|
|||||||
const forceLoginParam = searchParams.forceLogin;
|
const forceLoginParam = searchParams.forceLogin;
|
||||||
const forceLogin = forceLoginParam === "true";
|
const forceLogin = forceLoginParam === "true";
|
||||||
|
|
||||||
if (build !== "saas") {
|
const env = pullEnv();
|
||||||
|
|
||||||
|
if (build !== "saas" && !env.flags.useOrgOnlyIdp) {
|
||||||
redirect("/");
|
redirect("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
const env = pullEnv();
|
|
||||||
|
|
||||||
const authHeader = await authCookieHeader();
|
const authHeader = await authCookieHeader();
|
||||||
|
|
||||||
if (searchParams.token) {
|
if (searchParams.token) {
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ export default async function ResourceAuthPage(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let loginIdps: LoginFormIDP[] = [];
|
let loginIdps: LoginFormIDP[] = [];
|
||||||
if (build === "saas") {
|
if (build === "saas" || env.flags.useOrgOnlyIdp) {
|
||||||
if (subscribed) {
|
if (subscribed) {
|
||||||
const idpsRes = await cache(
|
const idpsRes = await cache(
|
||||||
async () =>
|
async () =>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { SidebarNavItem } from "@app/components/SidebarNav";
|
import { SidebarNavItem } from "@app/components/SidebarNav";
|
||||||
|
import { Env } from "@app/lib/types/env";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
import {
|
import {
|
||||||
Settings,
|
Settings,
|
||||||
@@ -39,7 +40,7 @@ export const orgLangingNavItems: SidebarNavItem[] = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
export const orgNavSections = (): SidebarNavSection[] => [
|
export const orgNavSections = (env?: Env): SidebarNavSection[] => [
|
||||||
{
|
{
|
||||||
heading: "sidebarGeneral",
|
heading: "sidebarGeneral",
|
||||||
items: [
|
items: [
|
||||||
@@ -92,8 +93,7 @@ export const orgNavSections = (): SidebarNavSection[] => [
|
|||||||
{
|
{
|
||||||
title: "sidebarRemoteExitNodes",
|
title: "sidebarRemoteExitNodes",
|
||||||
href: "/{orgId}/settings/remote-exit-nodes",
|
href: "/{orgId}/settings/remote-exit-nodes",
|
||||||
icon: <Server className="size-4 flex-none" />,
|
icon: <Server className="size-4 flex-none" />
|
||||||
showEE: true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
: [])
|
: [])
|
||||||
@@ -123,13 +123,12 @@ export const orgNavSections = (): 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" />
|
||||||
},
|
},
|
||||||
...(build == "saas"
|
...(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" />
|
||||||
showEE: true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
@@ -228,7 +227,7 @@ export const orgNavSections = (): SidebarNavSection[] => [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
export const adminNavSections: SidebarNavSection[] = [
|
export const adminNavSections = (env?: Env): SidebarNavSection[] => [
|
||||||
{
|
{
|
||||||
heading: "sidebarAdmin",
|
heading: "sidebarAdmin",
|
||||||
items: [
|
items: [
|
||||||
@@ -242,11 +241,15 @@ export const adminNavSections: SidebarNavSection[] = [
|
|||||||
href: "/admin/api-keys",
|
href: "/admin/api-keys",
|
||||||
icon: <KeyRound className="size-4 flex-none" />
|
icon: <KeyRound className="size-4 flex-none" />
|
||||||
},
|
},
|
||||||
{
|
...(build === "oss" || !env?.flags.useOrgOnlyIdp
|
||||||
title: "sidebarIdentityProviders",
|
? [
|
||||||
href: "/admin/idp",
|
{
|
||||||
icon: <Fingerprint className="size-4 flex-none" />
|
title: "sidebarIdentityProviders",
|
||||||
},
|
href: "/admin/idp",
|
||||||
|
icon: <Fingerprint className="size-4 flex-none" />
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: []),
|
||||||
...(build == "enterprise"
|
...(build == "enterprise"
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ export default function AuthPageBrandingForm({
|
|||||||
const brandingData = form.getValues();
|
const brandingData = form.getValues();
|
||||||
|
|
||||||
if (!isValid || !isPaidUser) return;
|
if (!isValid || !isPaidUser) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const updateRes = await api.put(
|
const updateRes = await api.put(
|
||||||
`/org/${orgId}/login-page-branding`,
|
`/org/${orgId}/login-page-branding`,
|
||||||
@@ -289,7 +290,8 @@ export default function AuthPageBrandingForm({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{build === "saas" && (
|
{build === "saas" ||
|
||||||
|
env.env.flags.useOrgOnlyIdp ? (
|
||||||
<>
|
<>
|
||||||
<div className="mt-3 mb-6">
|
<div className="mt-3 mb-6">
|
||||||
<SettingsSectionTitle>
|
<SettingsSectionTitle>
|
||||||
@@ -343,7 +345,7 @@ export default function AuthPageBrandingForm({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
) : null}
|
||||||
|
|
||||||
<div className="mt-3 mb-6">
|
<div className="mt-3 mb-6">
|
||||||
<SettingsSectionTitle>
|
<SettingsSectionTitle>
|
||||||
|
|||||||
@@ -63,7 +63,9 @@ export function pullEnv(): Env {
|
|||||||
disableProductHelpBanners:
|
disableProductHelpBanners:
|
||||||
process.env.FLAGS_DISABLE_PRODUCT_HELP_BANNERS === "true"
|
process.env.FLAGS_DISABLE_PRODUCT_HELP_BANNERS === "true"
|
||||||
? true
|
? true
|
||||||
: false
|
: false,
|
||||||
|
useOrgOnlyIdp:
|
||||||
|
process.env.USE_ORG_ONLY_IDP === "true" ? true : false
|
||||||
},
|
},
|
||||||
|
|
||||||
branding: {
|
branding: {
|
||||||
|
|||||||
@@ -157,7 +157,13 @@ export const orgQueries = {
|
|||||||
return res.data.data.domains;
|
return res.data.data.domains;
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
identityProviders: ({ orgId }: { orgId: string }) =>
|
identityProviders: ({
|
||||||
|
orgId,
|
||||||
|
useOrgOnlyIdp
|
||||||
|
}: {
|
||||||
|
orgId: string;
|
||||||
|
useOrgOnlyIdp?: boolean;
|
||||||
|
}) =>
|
||||||
queryOptions({
|
queryOptions({
|
||||||
queryKey: ["ORG", orgId, "IDPS"] as const,
|
queryKey: ["ORG", orgId, "IDPS"] as const,
|
||||||
queryFn: async ({ signal, meta }) => {
|
queryFn: async ({ signal, meta }) => {
|
||||||
@@ -165,7 +171,12 @@ export const orgQueries = {
|
|||||||
AxiosResponse<{
|
AxiosResponse<{
|
||||||
idps: { idpId: number; name: string }[];
|
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;
|
return res.data.data.idps;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export type Env = {
|
|||||||
hideSupporterKey: boolean;
|
hideSupporterKey: boolean;
|
||||||
usePangolinDns: boolean;
|
usePangolinDns: boolean;
|
||||||
disableProductHelpBanners: boolean;
|
disableProductHelpBanners: boolean;
|
||||||
|
useOrgOnlyIdp: boolean;
|
||||||
};
|
};
|
||||||
branding: {
|
branding: {
|
||||||
appName?: string;
|
appName?: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user