mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-19 19:36:38 +00:00
Compare commits
2 Commits
1.14.1
...
org-only-i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a537c6830 | ||
|
|
2810632f4a |
@@ -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";
|
||||||
|
|||||||
88
server/middlewares/integration/verifyApiKeyIdpAccess.ts
Normal file
88
server/middlewares/integration/verifyApiKeyIdpAccess.ts
Normal 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"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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({}),
|
||||||
|
|||||||
@@ -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
|
||||||
|
);
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
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>
|
||||||
|
|||||||
@@ -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"
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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