diff --git a/backend/src/server/routes/v1/admin-router.ts b/backend/src/server/routes/v1/admin-router.ts index e6da3ad73..bc0c725f0 100644 --- a/backend/src/server/routes/v1/admin-router.ts +++ b/backend/src/server/routes/v1/admin-router.ts @@ -29,6 +29,8 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => { }).extend({ isMigrationModeOn: z.boolean(), defaultAuthOrgSlug: z.string().nullable(), + defaultAuthOrgAuthEnforced: z.boolean().nullish(), + defaultAuthOrgAuthMethod: z.string().nullish(), isSecretScanningDisabled: z.boolean() }) }) diff --git a/backend/src/services/org/org-service.ts b/backend/src/services/org/org-service.ts index a751efe39..3d74f80b6 100644 --- a/backend/src/services/org/org-service.ts +++ b/backend/src/services/org/org-service.ts @@ -291,7 +291,7 @@ export const orgServiceFactory = ({ } if (authEnforced !== undefined) { - if (!plan?.samlSSO || !plan.oidcSSO) + if (!plan?.samlSSO && !plan.oidcSSO) throw new BadRequestError({ message: "Failed to enforce/un-enforce SSO due to plan restriction. Upgrade plan to enforce/un-enforce SSO." }); diff --git a/backend/src/services/super-admin/super-admin-dal.ts b/backend/src/services/super-admin/super-admin-dal.ts index 7e707e6fa..d7d11a5d2 100644 --- a/backend/src/services/super-admin/super-admin-dal.ts +++ b/backend/src/services/super-admin/super-admin-dal.ts @@ -14,9 +14,31 @@ export const superAdminDALFactory = (db: TDbClient) => { const config = await (tx || db)(TableName.SuperAdmin) .where(`${TableName.SuperAdmin}.id`, id) .leftJoin(TableName.Organization, `${TableName.SuperAdmin}.defaultAuthOrgId`, `${TableName.Organization}.id`) + .leftJoin(TableName.SamlConfig, (qb) => { + qb.on(`${TableName.SamlConfig}.orgId`, "=", `${TableName.Organization}.id`).andOn( + `${TableName.SamlConfig}.isActive`, + "=", + db.raw("true") + ); + }) + .leftJoin(TableName.OidcConfig, (qb) => { + qb.on(`${TableName.OidcConfig}.orgId`, "=", `${TableName.Organization}.id`).andOn( + `${TableName.OidcConfig}.isActive`, + "=", + db.raw("true") + ); + }) .select( db.ref("*").withSchema(TableName.SuperAdmin) as unknown as keyof TSuperAdmin, - db.ref("slug").withSchema(TableName.Organization).as("defaultAuthOrgSlug") + db.ref("slug").withSchema(TableName.Organization).as("defaultAuthOrgSlug"), + db.ref("authEnforced").withSchema(TableName.Organization).as("defaultAuthOrgAuthEnforced"), + db.raw(` + CASE + WHEN ${TableName.SamlConfig}."orgId" IS NOT NULL THEN 'saml' + WHEN ${TableName.OidcConfig}."orgId" IS NOT NULL THEN 'oidc' + ELSE NULL + END as "defaultAuthOrgAuthMethod" + `) ) .first(); @@ -27,7 +49,11 @@ export const superAdminDALFactory = (db: TDbClient) => { return { ...config, defaultAuthOrgSlug: config?.defaultAuthOrgSlug || null - } as TSuperAdmin & { defaultAuthOrgSlug: string | null }; + } as TSuperAdmin & { + defaultAuthOrgSlug: string | null; + defaultAuthOrgAuthEnforced?: boolean | null; + defaultAuthOrgAuthMethod?: string | null; + }; }; const updateById = async (id: string, data: TSuperAdminUpdate, tx?: Knex) => { diff --git a/backend/src/services/super-admin/super-admin-service.ts b/backend/src/services/super-admin/super-admin-service.ts index 7c02d0e23..12c25de91 100644 --- a/backend/src/services/super-admin/super-admin-service.ts +++ b/backend/src/services/super-admin/super-admin-service.ts @@ -29,7 +29,13 @@ type TSuperAdminServiceFactoryDep = { export type TSuperAdminServiceFactory = ReturnType; // eslint-disable-next-line -export let getServerCfg: () => Promise; +export let getServerCfg: () => Promise< + TSuperAdmin & { + defaultAuthOrgSlug: string | null; + defaultAuthOrgAuthEnforced?: boolean | null; + defaultAuthOrgAuthMethod?: string | null; + } +>; const ADMIN_CONFIG_KEY = "infisical-admin-cfg"; const ADMIN_CONFIG_KEY_EXP = 60; // 60s diff --git a/frontend/src/hooks/api/admin/types.ts b/frontend/src/hooks/api/admin/types.ts index cec0b614d..c95912b5e 100644 --- a/frontend/src/hooks/api/admin/types.ts +++ b/frontend/src/hooks/api/admin/types.ts @@ -19,6 +19,8 @@ export type TServerConfig = { isSecretScanningDisabled: boolean; defaultAuthOrgSlug: string | null; defaultAuthOrgId: string | null; + defaultAuthOrgAuthMethod?: string | null; + defaultAuthOrgAuthEnforced?: boolean | null; enabledLoginMethods: LoginMethod[]; }; diff --git a/frontend/src/hooks/api/users/types.ts b/frontend/src/hooks/api/users/types.ts index 39a0c7c5c..b1095b009 100644 --- a/frontend/src/hooks/api/users/types.ts +++ b/frontend/src/hooks/api/users/types.ts @@ -11,7 +11,8 @@ export enum AuthMethod { JUMPCLOUD_SAML = "jumpcloud-saml", KEYCLOAK_SAML = "keycloak-saml", LDAP = "ldap", - OIDC = "oidc" + OIDC = "oidc", + SAML = "saml" } export type User = { diff --git a/frontend/src/views/Login/components/InitialStep/InitialStep.tsx b/frontend/src/views/Login/components/InitialStep/InitialStep.tsx index e2f8f99b9..fa4f957a5 100644 --- a/frontend/src/views/Login/components/InitialStep/InitialStep.tsx +++ b/frontend/src/views/Login/components/InitialStep/InitialStep.tsx @@ -1,4 +1,4 @@ -import { FormEvent, useCallback, useEffect, useRef, useState } from "react"; +import { FormEvent, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import Link from "next/link"; import { useRouter } from "next/router"; @@ -17,6 +17,7 @@ import { Button, IconButton, Input, Tooltip } from "@app/components/v2"; import { useServerConfig } from "@app/context"; import { useFetchServerStatus } from "@app/hooks/api"; import { LoginMethod } from "@app/hooks/api/admin/types"; +import { AuthMethod } from "@app/hooks/api/users/types"; import { useNavigateToSelectOrganization } from "../../Login.utils"; @@ -51,17 +52,33 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }: router.push(redirectUrl); }; + const redirectToOidc = (orgSlug: string) => { + const callbackPort = queryParams.get("callback_port"); + const redirectUrl = `/api/v1/sso/oidc/login?orgSlug=${orgSlug}${ + callbackPort ? `&callbackPort=${callbackPort}` : "" + }`; + router.push(redirectUrl); + }; + useEffect(() => { if (serverDetails?.samlDefaultOrgSlug) redirectToSaml(serverDetails.samlDefaultOrgSlug); }, [serverDetails?.samlDefaultOrgSlug]); - const handleSaml = useCallback((step: number) => { + const handleSaml = () => { if (config.defaultAuthOrgSlug) { redirectToSaml(config.defaultAuthOrgSlug); } else { - setStep(step); + setStep(2); } - }, []); + }; + + const handleOidc = () => { + if (config.defaultAuthOrgSlug) { + redirectToOidc(config.defaultAuthOrgSlug); + } else { + setStep(3); + } + }; const shouldDisplayLoginMethod = (method: LoginMethod) => !config.enabledLoginMethods || config.enabledLoginMethods.includes(method); @@ -142,6 +159,46 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }: setIsLoading(false); }; + if (config.defaultAuthOrgAuthEnforced && config.defaultAuthOrgAuthMethod) { + return ( +
+

+ Login to Infisical +

+ + {config.defaultAuthOrgAuthMethod === AuthMethod.SAML && ( +
+ +
+ )} + {config.defaultAuthOrgAuthMethod === AuthMethod.OIDC && ( +
+ +
+ )} + + ); + } + return (
{ - handleSaml(2); - }} + onClick={handleSaml} leftIcon={} className="mx-0 h-10 w-full" > @@ -171,9 +226,7 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }: