import { arktypeResolver } from "@hookform/resolvers/arktype"; import { useMutation } from "@tanstack/react-query"; import { useNavigate } from "@tanstack/react-router"; import { type } from "arktype"; import { ShieldCheck, Plus } from "lucide-react"; import { useId } from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { updateSsoProviderAutoLinkingMutation } from "~/client/api-client/@tanstack/react-query.gen"; import { Button } from "~/client/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "~/client/components/ui/card"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from "~/client/components/ui/form"; import { Input } from "~/client/components/ui/input"; import { Switch } from "~/client/components/ui/switch"; import { authClient } from "~/client/lib/auth-client"; import { parseError } from "~/client/lib/errors"; import { useOrganizationContext } from "~/client/hooks/use-org-context"; const ssoProviderSchema = type({ providerId: "string>=1", issuer: "string>=1", domain: "string>=1", clientId: "string>=1", clientSecret: "string>=1", discoveryEndpoint: "string>=1", linkMatchingEmails: "boolean", }); type ProviderForm = typeof ssoProviderSchema.infer; export function CreateSsoProviderPage() { const navigate = useNavigate(); const formId = useId(); const { activeOrganization } = useOrganizationContext(); const form = useForm({ resolver: arktypeResolver(ssoProviderSchema), defaultValues: { providerId: "", issuer: "", domain: "", clientId: "", clientSecret: "", discoveryEndpoint: "", linkMatchingEmails: false, }, }); const updateProviderAutoLinking = useMutation({ ...updateSsoProviderAutoLinkingMutation(), }); const registerProvider = useMutation({ mutationFn: async (formData: ProviderForm) => { const { error } = await authClient.sso.register({ providerId: formData.providerId, issuer: formData.issuer, domain: formData.domain, organizationId: activeOrganization.id, oidcConfig: { clientId: formData.clientId, clientSecret: formData.clientSecret, discoveryEndpoint: formData.discoveryEndpoint, scopes: ["openid", "email", "profile"], }, }); if (error) throw error; await updateProviderAutoLinking .mutateAsync({ path: { providerId: formData.providerId }, body: { enabled: formData.linkMatchingEmails }, }) .catch((updateError) => { toast.warning("Auto-link setting could not be saved", { description: parseError(updateError)?.message, }); }); }, onSuccess: () => { toast.success("SSO provider registered successfully"); void navigate({ to: "/settings", search: { tab: "organization" } }); }, onError: (error) => { toast.error("Failed to register provider", { description: error.message }); }, }); return (
Register SSO Provider
registerProvider.mutate(values))} className="space-y-4" >
( Provider ID Unique identifier used in callback URLs. )} /> ( Organization Domain Used to discover providers during login. )} /> ( Issuer URL )} /> ( Discovery Endpoint )} /> ( Client ID )} /> ( Client Secret )} />
(
Link matching emails to existing accounts If enabled, users who sign in with this provider will automatically access their existing account when the email address matches.
)} />
); }