import { zodResolver } from '@hookform/resolvers/zod' import { Trash } from 'lucide-react' import { useEffect, useState } from 'react' import { SubmitHandler, useForm } from 'react-hook-form' import { toast } from 'sonner' import { Button, Card, CardContent, CardFooter, Form_Shadcn_, FormControl_Shadcn_, FormField_Shadcn_, Switch, } from 'ui' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader' import z from 'zod' import { AttributeMapping } from './AttributeMapping' import { JoinOrganizationOnSignup } from './JoinOrganizationOnSignup' import { SSODomains } from './SSODomains' import { SSOMetadata } from './SSOMetadata' import { ScaffoldContainer, ScaffoldSection } from '@/components/layouts/Scaffold' import AlertError from '@/components/ui/AlertError' import { InlineLink } from '@/components/ui/InlineLink' import { TextConfirmModal } from '@/components/ui/TextConfirmModalWrapper' import { UpgradeToPro } from '@/components/ui/UpgradeToPro' import { useOrganizationMembersQuery } from '@/data/organizations/organization-members-query' import { useSSOConfigCreateMutation } from '@/data/sso/sso-config-create-mutation' import { useSSOConfigDeleteMutation } from '@/data/sso/sso-config-delete-mutation' import { useOrgSSOConfigQuery } from '@/data/sso/sso-config-query' import { useSSOConfigUpdateMutation } from '@/data/sso/sso-config-update-mutation' import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' import { DOCS_URL } from '@/lib/constants' const FormSchema = z .object({ enabled: z.boolean(), domains: z .array( z.object({ value: z.string().trim().min(1, 'Please provide a domain'), }) ) .min(1, 'At least one domain is required'), metadataXmlUrl: z.string().trim().optional(), metadataXmlFile: z.string().trim().optional(), emailMapping: z.array(z.object({ value: z.string().trim().min(1, 'This field is required') })), userNameMapping: z.array(z.object({ value: z.string().trim() })), firstNameMapping: z.array(z.object({ value: z.string().trim() })), lastNameMapping: z.array(z.object({ value: z.string().trim() })), joinOrgOnSignup: z.boolean(), roleOnJoin: z.string().optional(), }) // set the error on both fields .refine((data) => data.metadataXmlUrl || data.metadataXmlFile, { message: 'Please provide either a metadata XML URL or upload a metadata XML file', path: ['metadataXmlUrl'], }) .refine((data) => data.metadataXmlUrl || data.metadataXmlFile, { message: 'Please provide either a metadata XML URL or upload a metadata XML file', path: ['metadataXmlFile'], }) export type SSOConfigFormSchema = z.infer const defaultValues = { enabled: false, domains: [{ value: '' }], metadataXmlUrl: '', metadataXmlFile: '', emailMapping: [{ value: '' }], userNameMapping: [{ value: '' }], firstNameMapping: [{ value: '' }], lastNameMapping: [{ value: '' }], joinOrgOnSignup: false, roleOnJoin: 'Developer', } export const SSOConfig = () => { const FORM_ID = 'sso-config-form' const { data: organization } = useSelectedOrganizationQuery() const { hasAccess: hasAccessToSso, isLoading: isLoadingEntitlement } = useCheckEntitlements('auth.platform.sso') const { data: ssoConfig, isPending: isLoadingSSOConfig, isSuccess, isError, error: configError, } = useOrgSSOConfigQuery({ orgSlug: organization?.slug }, { enabled: !!organization }) const { data: members = [] } = useOrganizationMembersQuery({ slug: organization?.slug }) const ssoMemberCount = members.filter((m) => m.is_sso_user === true).length const isSSOProviderNotFound = ssoConfig === null const form = useForm({ resolver: zodResolver(FormSchema), defaultValues, }) const isSSOEnabled = form.watch('enabled') const { mutate: createSSOConfig, isPending: isCreating } = useSSOConfigCreateMutation({ onSuccess: () => form.reset(), }) const { mutate: updateSSOConfig, isPending: isUpdating } = useSSOConfigUpdateMutation({ onSuccess: () => form.reset(), }) const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false) const { mutate: deleteSSOConfig, isPending: isDeleting } = useSSOConfigDeleteMutation({ onSuccess: () => { toast.success('Successfully deleted SSO configuration') setIsDeleteModalVisible(false) form.reset(defaultValues) }, }) const onSubmit: SubmitHandler = (values) => { const roleOnJoin = (values.roleOnJoin || 'Developer') as | 'Administrator' | 'Developer' | 'Owner' | 'Read-only' | undefined const payload = { slug: organization!.slug, config: { enabled: values.enabled, domains: values.domains.map((d) => d.value), metadata_xml_file: values.metadataXmlFile!, metadata_xml_url: values.metadataXmlUrl!, email_mapping: values.emailMapping.map((item) => item.value).filter(Boolean), first_name_mapping: values.firstNameMapping.map((item) => item.value).filter(Boolean), last_name_mapping: values.lastNameMapping.map((item) => item.value).filter(Boolean), user_name_mapping: values.userNameMapping.map((item) => item.value).filter(Boolean), join_org_on_signup_enabled: values.joinOrgOnSignup, join_org_on_signup_role: roleOnJoin, }, } if (!!ssoConfig) { updateSSOConfig(payload) } else { createSSOConfig(payload) } } const onDeleteSSOConfig = () => { if (!organization?.slug) return deleteSSOConfig({ slug: organization.slug }) } useEffect(() => { if (ssoConfig) { form.reset({ enabled: ssoConfig.enabled, domains: ssoConfig.domains.map((domain) => ({ value: domain })), metadataXmlUrl: ssoConfig.metadata_xml_url, metadataXmlFile: ssoConfig.metadata_xml_file, emailMapping: ssoConfig.email_mapping.map((email) => ({ value: email })), userNameMapping: ssoConfig.user_name_mapping?.map((userName) => ({ value: userName })), firstNameMapping: ssoConfig.first_name_mapping?.map((firstName) => ({ value: firstName })), lastNameMapping: ssoConfig.last_name_mapping?.map((lastName) => ({ value: lastName })), joinOrgOnSignup: ssoConfig.join_org_on_signup_enabled, roleOnJoin: ssoConfig.join_org_on_signup_role, }) } }, [ssoConfig, form]) return ( {isLoadingEntitlement || (hasAccessToSso && isLoadingSSOConfig) ? ( ) : isError && !isSSOProviderNotFound ? ( ) : !hasAccessToSso ? ( ) : isSuccess || isSSOProviderNotFound ? ( <>
( Enable and configure SSO for your organization. Learn more about SSO{' '} here . } > )} /> {(isSSOEnabled || ssoConfig) && ( <> )}
{!!ssoConfig && ( )}
{form.formState.isDirty && ( )}
setIsDeleteModalVisible(false)} >

You are about to delete the SSO provider for{' '} {ssoConfig?.domains?.[0]}.

{ssoMemberCount > 0 && (

{ssoMemberCount} organization member{ssoMemberCount !== 1 ? 's' : ''} {' '} who authenticate via SSO will be{' '} permanently removed from this organization.

)}

This action will:

  • Disable SSO authentication for this organization
  • Remove all members who signed up using SSO
  • Prevent future SSO-based sign-ins

This action cannot be undone. {' '} Members will need to be re-invited if you wish to restore their access.

) : null}
) }