diff --git a/backend/src/db/migrations/20240620142418_default-saml-ldap-org.ts b/backend/src/db/migrations/20240620142418_default-saml-ldap-org.ts new file mode 100644 index 000000000..fec132df4 --- /dev/null +++ b/backend/src/db/migrations/20240620142418_default-saml-ldap-org.ts @@ -0,0 +1,27 @@ +import { Knex } from "knex"; + +import { TableName } from "../schemas"; + +const DEFAULT_AUTH_ORG_ID_FIELD = "defaultAuthOrgId"; + +export async function up(knex: Knex): Promise { + const hasDefaultOrgColumn = await knex.schema.hasColumn(TableName.SuperAdmin, DEFAULT_AUTH_ORG_ID_FIELD); + + await knex.schema.alterTable(TableName.SuperAdmin, (t) => { + if (!hasDefaultOrgColumn) { + t.uuid(DEFAULT_AUTH_ORG_ID_FIELD).nullable(); + t.foreign(DEFAULT_AUTH_ORG_ID_FIELD).references("id").inTable(TableName.Organization).onDelete("SET NULL"); + } + }); +} + +export async function down(knex: Knex): Promise { + const hasDefaultOrgColumn = await knex.schema.hasColumn(TableName.SuperAdmin, DEFAULT_AUTH_ORG_ID_FIELD); + + await knex.schema.alterTable(TableName.SuperAdmin, (t) => { + if (hasDefaultOrgColumn) { + t.dropForeign([DEFAULT_AUTH_ORG_ID_FIELD]); + t.dropColumn(DEFAULT_AUTH_ORG_ID_FIELD); + } + }); +} diff --git a/backend/src/db/schemas/super-admin.ts b/backend/src/db/schemas/super-admin.ts index 87ba35c83..29e41c78e 100644 --- a/backend/src/db/schemas/super-admin.ts +++ b/backend/src/db/schemas/super-admin.ts @@ -17,7 +17,8 @@ export const SuperAdminSchema = z.object({ instanceId: z.string().uuid().default("00000000-0000-0000-0000-000000000000"), trustSamlEmails: z.boolean().default(false).nullable().optional(), trustLdapEmails: z.boolean().default(false).nullable().optional(), - trustOidcEmails: z.boolean().default(false).nullable().optional() + trustOidcEmails: z.boolean().default(false).nullable().optional(), + defaultAuthOrgId: z.string().uuid().nullable().optional() }); export type TSuperAdmin = z.infer; diff --git a/backend/src/server/routes/v1/admin-router.ts b/backend/src/server/routes/v1/admin-router.ts index ea701c828..24c7e2a6e 100644 --- a/backend/src/server/routes/v1/admin-router.ts +++ b/backend/src/server/routes/v1/admin-router.ts @@ -22,6 +22,7 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => { 200: z.object({ config: SuperAdminSchema.omit({ createdAt: true, updatedAt: true }).extend({ isMigrationModeOn: z.boolean(), + defaultAuthOrgSlug: z.string().nullable(), isSecretScanningDisabled: z.boolean() }) }) @@ -52,11 +53,14 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => { allowedSignUpDomain: z.string().optional().nullable(), trustSamlEmails: z.boolean().optional(), trustLdapEmails: z.boolean().optional(), - trustOidcEmails: z.boolean().optional() + trustOidcEmails: z.boolean().optional(), + defaultAuthOrgId: z.string().optional().nullable() }), response: { 200: z.object({ - config: SuperAdminSchema + config: SuperAdminSchema.extend({ + defaultAuthOrgSlug: z.string().nullable() + }) }) } }, diff --git a/backend/src/services/super-admin/super-admin-dal.ts b/backend/src/services/super-admin/super-admin-dal.ts index 64133ed7e..7e707e6fa 100644 --- a/backend/src/services/super-admin/super-admin-dal.ts +++ b/backend/src/services/super-admin/super-admin-dal.ts @@ -1,7 +1,57 @@ +import { Knex } from "knex"; + import { TDbClient } from "@app/db"; -import { TableName } from "@app/db/schemas"; +import { TableName, TSuperAdmin, TSuperAdminUpdate } from "@app/db/schemas"; +import { DatabaseError } from "@app/lib/errors"; import { ormify } from "@app/lib/knex"; export type TSuperAdminDALFactory = ReturnType; -export const superAdminDALFactory = (db: TDbClient) => ormify(db, TableName.SuperAdmin, {}); +export const superAdminDALFactory = (db: TDbClient) => { + const superAdminOrm = ormify(db, TableName.SuperAdmin); + + const findById = async (id: string, tx?: Knex) => { + const config = await (tx || db)(TableName.SuperAdmin) + .where(`${TableName.SuperAdmin}.id`, id) + .leftJoin(TableName.Organization, `${TableName.SuperAdmin}.defaultAuthOrgId`, `${TableName.Organization}.id`) + .select( + db.ref("*").withSchema(TableName.SuperAdmin) as unknown as keyof TSuperAdmin, + db.ref("slug").withSchema(TableName.Organization).as("defaultAuthOrgSlug") + ) + .first(); + + if (!config) { + return null; + } + + return { + ...config, + defaultAuthOrgSlug: config?.defaultAuthOrgSlug || null + } as TSuperAdmin & { defaultAuthOrgSlug: string | null }; + }; + + const updateById = async (id: string, data: TSuperAdminUpdate, tx?: Knex) => { + const updatedConfig = await (superAdminOrm || tx).transaction(async (trx: Knex) => { + await superAdminOrm.updateById(id, data, trx); + const config = await findById(id, trx); + + if (!config) { + throw new DatabaseError({ + error: "Failed to find updated super admin config", + message: "Failed to update super admin config", + name: "UpdateById" + }); + } + + return config; + }); + + return updatedConfig; + }; + + return { + ...superAdminOrm, + findById, + updateById + }; +}; diff --git a/backend/src/services/super-admin/super-admin-service.ts b/backend/src/services/super-admin/super-admin-service.ts index 27e198d85..41b97efa4 100644 --- a/backend/src/services/super-admin/super-admin-service.ts +++ b/backend/src/services/super-admin/super-admin-service.ts @@ -25,7 +25,7 @@ type TSuperAdminServiceFactoryDep = { export type TSuperAdminServiceFactory = ReturnType; // eslint-disable-next-line -export let getServerCfg: () => Promise; +export let getServerCfg: () => Promise; const ADMIN_CONFIG_KEY = "infisical-admin-cfg"; const ADMIN_CONFIG_KEY_EXP = 60; // 60s @@ -42,16 +42,20 @@ export const superAdminServiceFactory = ({ // TODO(akhilmhdh): bad pattern time less change this later to me itself getServerCfg = async () => { const config = await keyStore.getItem(ADMIN_CONFIG_KEY); + // missing in keystore means fetch from db if (!config) { const serverCfg = await serverCfgDAL.findById(ADMIN_CONFIG_DB_UUID); - if (serverCfg) { - await keyStore.setItemWithExpiry(ADMIN_CONFIG_KEY, ADMIN_CONFIG_KEY_EXP, JSON.stringify(serverCfg)); // insert it back to keystore + + if (!serverCfg) { + throw new BadRequestError({ name: "Admin config", message: "Admin config not found" }); } + + await keyStore.setItemWithExpiry(ADMIN_CONFIG_KEY, ADMIN_CONFIG_KEY_EXP, JSON.stringify(serverCfg)); // insert it back to keystore return serverCfg; } - const keyStoreServerCfg = JSON.parse(config) as TSuperAdmin; + const keyStoreServerCfg = JSON.parse(config) as TSuperAdmin & { defaultAuthOrgSlug: string | null }; return { ...keyStoreServerCfg, // this is to allow admin router to work @@ -65,14 +69,21 @@ export const superAdminServiceFactory = ({ const serverCfg = await serverCfgDAL.findById(ADMIN_CONFIG_DB_UUID); if (serverCfg) return; - // @ts-expect-error id is kept as fixed for idempotence and to avoid race condition - const newCfg = await serverCfgDAL.create({ initialized: false, allowSignUp: true, id: ADMIN_CONFIG_DB_UUID }); + const newCfg = await serverCfgDAL.create({ + // @ts-expect-error id is kept as fixed for idempotence and to avoid race condition + id: ADMIN_CONFIG_DB_UUID, + initialized: false, + allowSignUp: true, + defaultAuthOrgId: null + }); return newCfg; }; const updateServerCfg = async (data: TSuperAdminUpdate) => { const updatedServerCfg = await serverCfgDAL.updateById(ADMIN_CONFIG_DB_UUID, data); + await keyStore.setItemWithExpiry(ADMIN_CONFIG_KEY, ADMIN_CONFIG_KEY_EXP, JSON.stringify(updatedServerCfg)); + return updatedServerCfg; }; diff --git a/frontend/src/components/v2/Select/Select.tsx b/frontend/src/components/v2/Select/Select.tsx index 29dba23c7..015b16f98 100644 --- a/frontend/src/components/v2/Select/Select.tsx +++ b/frontend/src/components/v2/Select/Select.tsx @@ -36,61 +36,73 @@ export const Select = forwardRef( ref ): JSX.Element => { return ( - - - - {props.icon ? : placeholder} - +
+ { + if (!props.onValueChange) return; - - - - - - + - -
- -
-
- - {isLoading ? ( -
- - Loading... -
- ) : ( - children +
+ {props.icon && } + +
+ + + + +
+ + - -
- -
-
-
-
-
+ position={position} + style={{ width: "var(--radix-select-trigger-width)" }} + > + +
+ +
+
+ + {isLoading ? ( +
+ + Loading... +
+ ) : ( + children + )} +
+ +
+ +
+
+ + + +
); } ); @@ -114,7 +126,7 @@ export const SelectItem = forwardRef( outline-none transition-all hover:bg-mineshaft-500 data-[highlighted]:bg-mineshaft-700/80`, isSelected && "bg-primary", isDisabled && - "cursor-not-allowed text-gray-600 hover:bg-transparent hover:text-mineshaft-600", + "cursor-not-allowed text-gray-600 hover:bg-transparent hover:text-mineshaft-600", className )} ref={forwardedRef} @@ -129,3 +141,45 @@ export const SelectItem = forwardRef( ); SelectItem.displayName = "SelectItem"; + +export type SelectClearProps = Omit & { + onClear: () => void; + selectValue: string; +}; + +export const SelectClear = forwardRef( + ( + { children, className, isSelected, isDisabled, onClear, selectValue, ...props }, + forwardedRef + ) => { + return ( + onClear()} + onClick={() => onClear()} + className={twMerge( + `relative mb-0.5 flex + cursor-pointer select-none items-center rounded-md py-2 pl-10 pr-4 text-sm + outline-none transition-all hover:bg-mineshaft-500 data-[highlighted]:bg-mineshaft-700/80`, + isSelected && "bg-primary", + isDisabled && + "cursor-not-allowed text-gray-600 hover:bg-transparent hover:text-mineshaft-600", + className + )} + ref={forwardedRef} + > +
+ +
+ {children} +
+ ); + } +); +SelectClear.displayName = "SelectClear"; diff --git a/frontend/src/components/v2/Select/index.tsx b/frontend/src/components/v2/Select/index.tsx index 6a783605a..3765851d5 100644 --- a/frontend/src/components/v2/Select/index.tsx +++ b/frontend/src/components/v2/Select/index.tsx @@ -1,2 +1,2 @@ export type { SelectItemProps, SelectProps } from "./Select"; -export { Select, SelectItem } from "./Select"; +export { Select, SelectClear, SelectItem } from "./Select"; diff --git a/frontend/src/hooks/api/admin/types.ts b/frontend/src/hooks/api/admin/types.ts index 0d06a2aa1..524bc6ace 100644 --- a/frontend/src/hooks/api/admin/types.ts +++ b/frontend/src/hooks/api/admin/types.ts @@ -7,6 +7,8 @@ export type TServerConfig = { trustLdapEmails: boolean; trustOidcEmails: boolean; isSecretScanningDisabled: boolean; + defaultAuthOrgSlug: string | null; + defaultAuthOrgId: string | null; }; export type TCreateAdminUserDTO = { diff --git a/frontend/src/hooks/api/serverDetails/types.ts b/frontend/src/hooks/api/serverDetails/types.ts index 911526404..3e22c2684 100644 --- a/frontend/src/hooks/api/serverDetails/types.ts +++ b/frontend/src/hooks/api/serverDetails/types.ts @@ -4,5 +4,5 @@ export type ServerStatus = { emailConfigured: boolean; secretScanningConfigured: boolean; redisConfigured: boolean; - samlDefaultOrgSlug: boolean + samlDefaultOrgSlug: string; }; diff --git a/frontend/src/views/Login/Login.tsx b/frontend/src/views/Login/Login.tsx index cad207aa8..36cd355f5 100644 --- a/frontend/src/views/Login/Login.tsx +++ b/frontend/src/views/Login/Login.tsx @@ -1,16 +1,15 @@ import { useEffect, useState } from "react"; -import { useRouter } from "next/router"; import { isLoggedIn } from "@app/reactQuery"; import { InitialStep, MFAStep, SSOStep } from "./components"; -import { navigateUserToSelectOrg } from "./Login.utils"; +import { useNavigateToSelectOrganization } from "./Login.utils"; export const Login = () => { - const router = useRouter(); const [step, setStep] = useState(0); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); + const { navigateToSelectOrganization } = useNavigateToSelectOrganization(); const queryParams = new URLSearchParams(window.location.search); @@ -21,10 +20,10 @@ export const Login = () => { const callbackPort = queryParams?.get("callback_port"); // case: a callback port is set, meaning it's a cli login request: redirect to select org with callback port if (callbackPort) { - navigateUserToSelectOrg(router, callbackPort); + navigateToSelectOrganization(callbackPort); } else { // case: no callback port, meaning it's a regular login request: redirect to select org - navigateUserToSelectOrg(router); + navigateToSelectOrganization(); } } catch (error) { console.log("Error - Not logged in yet"); diff --git a/frontend/src/views/Login/Login.utils.tsx b/frontend/src/views/Login/Login.utils.tsx index b6e3c1a10..00f6037f1 100644 --- a/frontend/src/views/Login/Login.utils.tsx +++ b/frontend/src/views/Login/Login.utils.tsx @@ -1,5 +1,7 @@ -import { NextRouter } from "next/router"; +import { NextRouter, useRouter } from "next/router"; +import { useServerConfig } from "@app/context"; +import { useSelectOrganization } from "@app/hooks/api"; import { fetchOrganizations } from "@app/hooks/api/organization/queries"; import { userKeys } from "@app/hooks/api/users/queries"; import { queryClient } from "@app/reactQuery"; @@ -27,14 +29,29 @@ export const navigateUserToOrg = async (router: NextRouter, organizationId?: str } }; -export const navigateUserToSelectOrg = (router: NextRouter, cliCallbackPort?: string) => { - queryClient.invalidateQueries(userKeys.getUser); +export const useNavigateToSelectOrganization = () => { + const { config } = useServerConfig(); + const selectOrganization = useSelectOrganization(); + const router = useRouter(); - let redirectTo = "/login/select-organization"; + const navigate = async (cliCallbackPort?: string) => { + if (config.defaultAuthOrgId) { + await selectOrganization.mutateAsync({ + organizationId: config.defaultAuthOrgId + }); - if (cliCallbackPort) { - redirectTo += `?callback_port=${cliCallbackPort}`; - } + await navigateUserToOrg(router, config.defaultAuthOrgId); + } - router.push(redirectTo, undefined, { shallow: true }); + queryClient.invalidateQueries(userKeys.getUser); + let redirectTo = "/login/select-organization"; + + if (cliCallbackPort) { + redirectTo += `?callback_port=${cliCallbackPort}`; + } + + router.push(redirectTo, undefined, { shallow: true }); + }; + + return { navigateToSelectOrganization: navigate }; }; diff --git a/frontend/src/views/Login/LoginLDAP.tsx b/frontend/src/views/Login/LoginLDAP.tsx index 021ac7334..1e99c611d 100644 --- a/frontend/src/views/Login/LoginLDAP.tsx +++ b/frontend/src/views/Login/LoginLDAP.tsx @@ -4,15 +4,19 @@ import { useRouter } from "next/router"; import { createNotification } from "@app/components/notifications"; import { Button, Input } from "@app/components/v2"; +import { useServerConfig } from "@app/context"; import { loginLDAPRedirect } from "@app/hooks/api/auth/queries"; export const LoginLDAP = () => { const router = useRouter(); + const { config } = useServerConfig(); const queryParams = new URLSearchParams(window.location.search); const passedOrgSlug = queryParams.get("organizationSlug"); const passedUsername = queryParams.get("username"); - const [organizationSlug, setOrganizationSlug] = useState(passedOrgSlug || ""); + const [organizationSlug, setOrganizationSlug] = useState( + config.defaultAuthOrgSlug || passedOrgSlug || "" + ); const [username, setUsername] = useState(passedUsername || ""); const [password, setPassword] = useState(""); @@ -63,21 +67,22 @@ export const LoginLDAP = () => { What's your LDAP Login?

-
-
- setOrganizationSlug(e.target.value)} - type="text" - placeholder="Enter your organization slug..." - isRequired - autoComplete="email" - id="email" - className="h-12" - isDisabled={passedOrgSlug !== null} - /> + {!config.defaultAuthOrgSlug && !passedOrgSlug && ( +
+
+ setOrganizationSlug(e.target.value)} + type="text" + placeholder="Enter your organization slug..." + isRequired + autoComplete="email" + id="email" + className="h-12" + /> +
-
+ )}
void; @@ -39,16 +39,28 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }: const captchaRef = useRef(null); const { data: serverDetails } = useFetchServerStatus(); + const { navigateToSelectOrganization } = useNavigateToSelectOrganization(); + + const redirectToSaml = (orgSlug: string) => { + const callbackPort = queryParams.get("callback_port"); + const redirectUrl = `/api/v1/sso/redirect/saml2/organizations/${orgSlug}${ + callbackPort ? `?callback_port=${callbackPort}` : "" + }`; + router.push(redirectUrl); + }; + useEffect(() => { - if (serverDetails?.samlDefaultOrgSlug) { - const callbackPort = queryParams.get("callback_port"); - const redirectUrl = `/api/v1/sso/redirect/saml2/organizations/${ - serverDetails?.samlDefaultOrgSlug - }${callbackPort ? `?callback_port=${callbackPort}` : ""}`; - router.push(redirectUrl); - } + if (serverDetails?.samlDefaultOrgSlug) redirectToSaml(serverDetails.samlDefaultOrgSlug); }, [serverDetails?.samlDefaultOrgSlug]); + const handleSaml = useCallback((step: number) => { + if (config.defaultAuthOrgSlug) { + redirectToSaml(config.defaultAuthOrgSlug); + } else { + setStep(step); + } + }, []); + const handleLogin = async (e: FormEvent) => { e.preventDefault(); try { @@ -75,7 +87,7 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }: return; } - navigateUserToSelectOrg(router, callbackPort!); + navigateToSelectOrganization(callbackPort!); } else { setLoginError(true); createNotification({ @@ -100,7 +112,7 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }: return; } - navigateUserToSelectOrg(router); + navigateToSelectOrganization(); // case: login does not require MFA step createNotification({ @@ -211,7 +223,7 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }: colorSchema="primary" variant="outline_bg" onClick={() => { - setStep(2); + handleSaml(2); }} leftIcon={} className="mx-0 h-10 w-full" diff --git a/frontend/src/views/Login/components/MFAStep/MFAStep.tsx b/frontend/src/views/Login/components/MFAStep/MFAStep.tsx index 5e613008d..5f04454bc 100644 --- a/frontend/src/views/Login/components/MFAStep/MFAStep.tsx +++ b/frontend/src/views/Login/components/MFAStep/MFAStep.tsx @@ -16,7 +16,7 @@ import { useSelectOrganization, verifyMfaToken } from "@app/hooks/api/auth/queri import { fetchOrganizations } from "@app/hooks/api/organization/queries"; import { fetchMyPrivateKey } from "@app/hooks/api/users/queries"; -import { navigateUserToOrg, navigateUserToSelectOrg } from "../../Login.utils"; +import { navigateUserToOrg, useNavigateToSelectOrganization } from "../../Login.utils"; // The style for the verification code input const props = { @@ -50,6 +50,7 @@ export const MFAStep = ({ email, password, providerAuthToken }: Props) => { const [isLoading, setIsLoading] = useState(false); const [isLoadingResend, setIsLoadingResend] = useState(false); const [mfaCode, setMfaCode] = useState(""); + const { navigateToSelectOrganization } = useNavigateToSelectOrganization(); const [triesLeft, setTriesLeft] = useState(undefined); const { t } = useTranslation(); @@ -93,7 +94,7 @@ export const MFAStep = ({ email, password, providerAuthToken }: Props) => { // case: user has orgs, so we navigate the user to select an org if (userOrgs.length > 0) { - navigateUserToSelectOrg(router, callbackPort); + navigateToSelectOrganization(callbackPort); } // case: no orgs found, so we navigate the user to create an org // cli login will fail in this case @@ -166,7 +167,7 @@ export const MFAStep = ({ email, password, providerAuthToken }: Props) => { // case: user has orgs, so we navigate the user to select an org if (userOrgs.length > 0) { - navigateUserToSelectOrg(router, callbackPort); + navigateToSelectOrganization(callbackPort); } // case: no orgs found, so we navigate the user to create an org // cli login will fail in this case @@ -195,7 +196,7 @@ export const MFAStep = ({ email, password, providerAuthToken }: Props) => { if (organizationId) { await navigateUserToOrg(router, organizationId); } else { - navigateUserToSelectOrg(router); + navigateToSelectOrganization(); } } else { createNotification({ diff --git a/frontend/src/views/Login/components/PasswordStep/PasswordStep.tsx b/frontend/src/views/Login/components/PasswordStep/PasswordStep.tsx index 06438a2f8..10be08f4c 100644 --- a/frontend/src/views/Login/components/PasswordStep/PasswordStep.tsx +++ b/frontend/src/views/Login/components/PasswordStep/PasswordStep.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef,useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import Link from "next/link"; import { useRouter } from "next/router"; @@ -16,7 +16,7 @@ import { useOauthTokenExchange, useSelectOrganization } from "@app/hooks/api"; import { fetchOrganizations } from "@app/hooks/api/organization/queries"; import { fetchMyPrivateKey } from "@app/hooks/api/users/queries"; -import { navigateUserToOrg, navigateUserToSelectOrg } from "../../Login.utils"; +import { navigateUserToOrg, useNavigateToSelectOrganization } from "../../Login.utils"; type Props = { providerAuthToken: string; @@ -39,8 +39,11 @@ export const PasswordStep = ({ const { mutateAsync: selectOrganization } = useSelectOrganization(); const { mutateAsync: oauthTokenExchange } = useOauthTokenExchange(); - const { callbackPort, organizationId, hasExchangedPrivateKey } = - jwt_decode(providerAuthToken) as any; + const { navigateToSelectOrganization } = useNavigateToSelectOrganization(); + + const { callbackPort, organizationId, hasExchangedPrivateKey } = jwt_decode( + providerAuthToken + ) as any; const handleExchange = async () => { try { @@ -92,7 +95,7 @@ export const PasswordStep = ({ // case: user has orgs, so we navigate the user to select an org if (userOrgs.length > 0) { - navigateUserToSelectOrg(router, callbackPort); + navigateToSelectOrganization(callbackPort); } // case: no orgs found, so we navigate the user to create an org else { @@ -176,7 +179,7 @@ export const PasswordStep = ({ // case: user has orgs, so we navigate the user to select an org if (userOrgs.length > 0) { - navigateUserToSelectOrg(router, callbackPort); + navigateToSelectOrganization(callbackPort); } // case: no orgs found, so we navigate the user to create an org else { @@ -220,7 +223,7 @@ export const PasswordStep = ({ const userOrgs = await fetchOrganizations(); if (userOrgs.length > 0) { - navigateUserToSelectOrg(router); + navigateToSelectOrganization(); } else { await navigateUserToOrg(router); } @@ -270,7 +273,7 @@ export const PasswordStep = ({

- What's your Infisical password? + What's your Infisical password?

diff --git a/frontend/src/views/admin/DashboardPage/DashboardPage.tsx b/frontend/src/views/admin/DashboardPage/DashboardPage.tsx index f16c236b4..ef4ff099d 100644 --- a/frontend/src/views/admin/DashboardPage/DashboardPage.tsx +++ b/frontend/src/views/admin/DashboardPage/DashboardPage.tsx @@ -13,6 +13,7 @@ import { FormControl, Input, Select, + SelectClear, SelectItem, Switch, Tab, @@ -21,7 +22,7 @@ import { Tabs } from "@app/components/v2"; import { useOrganization, useServerConfig, useUser } from "@app/context"; -import { useUpdateServerConfig } from "@app/hooks/api"; +import { useGetOrganizations, useUpdateServerConfig } from "@app/hooks/api"; import { RateLimitPanel } from "./RateLimitPanel"; @@ -40,7 +41,8 @@ const formSchema = z.object({ allowedSignUpDomain: z.string().optional().nullable(), trustSamlEmails: z.boolean(), trustLdapEmails: z.boolean(), - trustOidcEmails: z.boolean() + trustOidcEmails: z.boolean(), + defaultAuthOrgId: z.string() }); type TDashboardForm = z.infer; @@ -62,16 +64,20 @@ export const AdminDashboardPage = () => { allowedSignUpDomain: config.allowedSignUpDomain, trustSamlEmails: config.trustSamlEmails, trustLdapEmails: config.trustLdapEmails, - trustOidcEmails: config.trustOidcEmails + trustOidcEmails: config.trustOidcEmails, + defaultAuthOrgId: config.defaultAuthOrgId ?? "" } }); - const signupMode = watch("signUpMode"); + const signUpMode = watch("signUpMode"); + const defaultAuthOrgId = watch("defaultAuthOrgId"); const { user, isLoading: isUserLoading } = useUser(); const { orgs } = useOrganization(); const { mutateAsync: updateServerConfig } = useUpdateServerConfig(); + const organizations = useGetOrganizations(); + const isNotAllowed = !user?.superAdmin; // TODO(akhilmhdh): on nextjs 14 roadmap this will be properly addressed with context split @@ -86,10 +92,10 @@ export const AdminDashboardPage = () => { const onFormSubmit = async (formData: TDashboardForm) => { try { - const { signUpMode, allowedSignUpDomain, trustSamlEmails, trustLdapEmails, trustOidcEmails } = - formData; + const { allowedSignUpDomain, trustSamlEmails, trustLdapEmails, trustOidcEmails } = formData; await updateServerConfig({ + defaultAuthOrgId: defaultAuthOrgId || null, allowSignUp: signUpMode !== SignUpModes.Disabled, allowedSignUpDomain: signUpMode === SignUpModes.Anyone ? allowedSignUpDomain : null, trustSamlEmails, @@ -130,7 +136,7 @@ export const AdminDashboardPage = () => {
@@ -146,13 +152,13 @@ export const AdminDashboardPage = () => { name="signUpMode" render={({ field: { onChange, ...field }, fieldState: { error } }) => ( onChange(e)} + {...field} + > + { + console.log("clearing"); + onChange(""); + }} + > + Allow all organizations + + {organizations.data?.map((org) => ( + + {org.name} + + ))} + + + )} + /> +
+ +
Trust emails
Select if you want Infisical to trust external emails from SAML/LDAP/OIDC