Compare commits

...

2 Commits

Author SHA1 Message Date
miloschwartz
0a537c6830 add org only idp to integration api 2026-01-11 10:47:19 -08:00
miloschwartz
2810632f4a add flag to enable org only idp in ee 2026-01-07 20:40:59 -08:00
25 changed files with 317 additions and 122 deletions

View File

@@ -13,3 +13,4 @@ export * from "./verifyApiKeyIsRoot";
export * from "./verifyApiKeyApiKeyAccess"; export * from "./verifyApiKeyApiKeyAccess";
export * from "./verifyApiKeyClientAccess"; export * from "./verifyApiKeyClientAccess";
export * from "./verifyApiKeySiteResourceAccess"; export * from "./verifyApiKeySiteResourceAccess";
export * from "./verifyApiKeyIdpAccess";

View File

@@ -0,0 +1,88 @@
import { Request, Response, NextFunction } from "express";
import { db } from "@server/db";
import { idp, idpOrg, apiKeyOrg } from "@server/db";
import { and, eq } from "drizzle-orm";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
export async function verifyApiKeyIdpAccess(
req: Request,
res: Response,
next: NextFunction
) {
try {
const apiKey = req.apiKey;
const idpId = req.params.idpId || req.body.idpId || req.query.idpId;
const orgId = req.params.orgId;
if (!apiKey) {
return next(
createHttpError(HttpCode.UNAUTHORIZED, "Key not authenticated")
);
}
if (!orgId) {
return next(
createHttpError(HttpCode.BAD_REQUEST, "Invalid organization ID")
);
}
if (!idpId) {
return next(
createHttpError(HttpCode.BAD_REQUEST, "Invalid IDP ID")
);
}
if (apiKey.isRoot) {
// Root keys can access any IDP in any org
return next();
}
const [idpRes] = await db
.select()
.from(idp)
.innerJoin(idpOrg, eq(idp.idpId, idpOrg.idpId))
.where(and(eq(idp.idpId, idpId), eq(idpOrg.orgId, orgId)))
.limit(1);
if (!idpRes || !idpRes.idp || !idpRes.idpOrg) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`IdP with ID ${idpId} not found for organization ${orgId}`
)
);
}
if (!req.apiKeyOrg) {
const apiKeyOrgRes = await db
.select()
.from(apiKeyOrg)
.where(
and(
eq(apiKeyOrg.apiKeyId, apiKey.apiKeyId),
eq(apiKeyOrg.orgId, idpRes.idpOrg.orgId)
)
);
req.apiKeyOrg = apiKeyOrgRes[0];
}
if (!req.apiKeyOrg) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"Key does not have access to this organization"
)
);
}
return next();
} catch (error) {
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"Error verifying IDP access"
)
);
}
}

View File

@@ -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() {

View File

@@ -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({}),

View File

@@ -18,7 +18,8 @@ import * as logs from "#private/routers/auditLogs";
import { import {
verifyApiKeyHasAction, verifyApiKeyHasAction,
verifyApiKeyIsRoot, verifyApiKeyIsRoot,
verifyApiKeyOrgAccess verifyApiKeyOrgAccess,
verifyApiKeyIdpAccess
} from "@server/middlewares"; } from "@server/middlewares";
import { import {
verifyValidSubscription, verifyValidSubscription,
@@ -31,6 +32,8 @@ import {
authenticated as a authenticated as a
} from "@server/routers/integration"; } from "@server/routers/integration";
import { logActionAudit } from "#private/middlewares"; import { logActionAudit } from "#private/middlewares";
import config from "#private/lib/config";
import { build } from "@server/build";
export const unauthenticated = ua; export const unauthenticated = ua;
export const authenticated = a; export const authenticated = a;
@@ -88,3 +91,49 @@ authenticated.get(
logActionAudit(ActionsEnum.exportLogs), logActionAudit(ActionsEnum.exportLogs),
logs.exportAccessAuditLogs logs.exportAccessAuditLogs
); );
authenticated.put(
"/org/:orgId/idp/oidc",
verifyValidLicense,
verifyApiKeyOrgAccess,
verifyApiKeyHasAction(ActionsEnum.createIdp),
logActionAudit(ActionsEnum.createIdp),
orgIdp.createOrgOidcIdp
);
authenticated.post(
"/org/:orgId/idp/:idpId/oidc",
verifyValidLicense,
verifyApiKeyOrgAccess,
verifyApiKeyIdpAccess,
verifyApiKeyHasAction(ActionsEnum.updateIdp),
logActionAudit(ActionsEnum.updateIdp),
orgIdp.updateOrgOidcIdp
);
authenticated.delete(
"/org/:orgId/idp/:idpId",
verifyValidLicense,
verifyApiKeyOrgAccess,
verifyApiKeyIdpAccess,
verifyApiKeyHasAction(ActionsEnum.deleteIdp),
logActionAudit(ActionsEnum.deleteIdp),
orgIdp.deleteOrgIdp
);
authenticated.get(
"/org/:orgId/idp/:idpId",
verifyValidLicense,
verifyApiKeyOrgAccess,
verifyApiKeyIdpAccess,
verifyApiKeyHasAction(ActionsEnum.getIdp),
orgIdp.getOrgIdp
);
authenticated.get(
"/org/:orgId/idp",
verifyValidLicense,
verifyApiKeyOrgAccess,
verifyApiKeyHasAction(ActionsEnum.listIdps),
orgIdp.listOrgIdps
);

View File

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

View File

@@ -46,22 +46,23 @@ const bodySchema = z.strictObject({
roleMapping: z.string().optional() roleMapping: z.string().optional()
}); });
// registry.registerPath({ registry.registerPath({
// method: "put", method: "put",
// path: "/idp/oidc", path: "/org/{orgId}/idp/oidc",
// description: "Create an OIDC IdP.", description: "Create an OIDC IdP for a specific organization.",
// tags: [OpenAPITags.Idp], tags: [OpenAPITags.Idp, OpenAPITags.Org],
// request: { request: {
// body: { params: paramsSchema,
// content: { body: {
// "application/json": { content: {
// schema: bodySchema "application/json": {
// } schema: bodySchema
// } }
// } }
// }, }
// responses: {} },
// }); responses: {}
});
export async function createOrgOidcIdp( export async function createOrgOidcIdp(
req: Request, req: Request,

View File

@@ -32,9 +32,9 @@ const paramsSchema = z
registry.registerPath({ registry.registerPath({
method: "delete", method: "delete",
path: "/idp/{idpId}", path: "/org/{orgId}/idp/{idpId}",
description: "Delete IDP.", description: "Delete IDP for a specific organization.",
tags: [OpenAPITags.Idp], tags: [OpenAPITags.Idp, OpenAPITags.Org],
request: { request: {
params: paramsSchema params: paramsSchema
}, },

View File

@@ -48,16 +48,16 @@ async function query(idpId: number, orgId: string) {
return res; return res;
} }
// registry.registerPath({ registry.registerPath({
// method: "get", method: "get",
// path: "/idp/{idpId}", path: "/org/:orgId/idp/:idpId",
// description: "Get an IDP by its IDP ID.", description: "Get an IDP by its IDP ID for a specific organization.",
// tags: [OpenAPITags.Idp], tags: [OpenAPITags.Idp, OpenAPITags.Org],
// request: { request: {
// params: paramsSchema params: paramsSchema
// }, },
// responses: {} responses: {}
// }); });
export async function getOrgIdp( export async function getOrgIdp(
req: Request, req: Request,

View File

@@ -62,16 +62,17 @@ async function query(orgId: string, limit: number, offset: number) {
return res; return res;
} }
// registry.registerPath({ registry.registerPath({
// method: "get", method: "get",
// path: "/idp", path: "/org/{orgId}/idp",
// description: "List all IDP in the system.", description: "List all IDP for a specific organization.",
// tags: [OpenAPITags.Idp], tags: [OpenAPITags.Idp, OpenAPITags.Org],
// request: { request: {
// query: querySchema query: querySchema,
// }, params: paramsSchema
// responses: {} },
// }); responses: {}
});
export async function listOrgIdps( export async function listOrgIdps(
req: Request, req: Request,

View File

@@ -53,23 +53,23 @@ export type UpdateOrgIdpResponse = {
idpId: number; idpId: number;
}; };
// registry.registerPath({ registry.registerPath({
// method: "post", method: "post",
// path: "/idp/{idpId}/oidc", path: "/org/{orgId}/idp/{idpId}/oidc",
// description: "Update an OIDC IdP.", description: "Update an OIDC IdP for a specific organization.",
// tags: [OpenAPITags.Idp], tags: [OpenAPITags.Idp, OpenAPITags.Org],
// request: { request: {
// params: paramsSchema, params: paramsSchema,
// body: { body: {
// content: { content: {
// "application/json": { "application/json": {
// schema: bodySchema schema: bodySchema
// } }
// } }
// } }
// }, },
// responses: {} responses: {}
// }); });
export async function updateOrgOidcIdp( export async function updateOrgOidcIdp(
req: Request, req: Request,

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 <Layout
orgId={params.orgId} orgId={params.orgId}
orgs={orgs} orgs={orgs}
navItems={orgNavSections()} navItems={orgNavSections(env)}
> >
{children} {children}
</Layout> </Layout>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 () =>

View File

@@ -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"
? [ ? [
{ {

View File

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

View File

@@ -114,6 +114,16 @@ function getActionsCategories(root: boolean) {
} }
}; };
if (root || build === "saas" || env.flags.useOrgOnlyIdp) {
actionsByCategory["Identity Provider (IDP)"] = {
[t("actionCreateIdp")]: "createIdp",
[t("actionUpdateIdp")]: "updateIdp",
[t("actionDeleteIdp")]: "deleteIdp",
[t("actionListIdps")]: "listIdps",
[t("actionGetIdp")]: "getIdp"
};
}
if (root) { if (root) {
actionsByCategory["Organization"] = { actionsByCategory["Organization"] = {
[t("actionListOrgs")]: "listOrgs", [t("actionListOrgs")]: "listOrgs",
@@ -128,24 +138,21 @@ function getActionsCategories(root: boolean) {
...actionsByCategory["Organization"] ...actionsByCategory["Organization"]
}; };
actionsByCategory["Identity Provider (IDP)"] = { actionsByCategory["Identity Provider (IDP)"][t("actionCreateIdpOrg")] =
[t("actionCreateIdp")]: "createIdp", "createIdpOrg";
[t("actionUpdateIdp")]: "updateIdp", actionsByCategory["Identity Provider (IDP)"][t("actionDeleteIdpOrg")] =
[t("actionDeleteIdp")]: "deleteIdp", "deleteIdpOrg";
[t("actionListIdps")]: "listIdps", actionsByCategory["Identity Provider (IDP)"][t("actionListIdpOrgs")] =
[t("actionGetIdp")]: "getIdp", "listIdpOrgs";
[t("actionCreateIdpOrg")]: "createIdpOrg", actionsByCategory["Identity Provider (IDP)"][t("actionUpdateIdpOrg")] =
[t("actionDeleteIdpOrg")]: "deleteIdpOrg", "updateIdpOrg";
[t("actionListIdpOrgs")]: "listIdpOrgs",
[t("actionUpdateIdpOrg")]: "updateIdpOrg"
};
actionsByCategory["User"] = { actionsByCategory["User"] = {
[t("actionUpdateUser")]: "updateUser", [t("actionUpdateUser")]: "updateUser",
[t("actionGetUser")]: "getUser" [t("actionGetUser")]: "getUser"
}; };
if (build == "saas") { if (build === "saas") {
actionsByCategory["SAAS"] = { actionsByCategory["SAAS"] = {
["Send Usage Notification Email"]: "sendUsageNotification" ["Send Usage Notification Email"]: "sendUsageNotification"
}; };

View File

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

View File

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

View File

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