diff --git a/ui/actions/integrations/saml.ts b/ui/actions/integrations/saml.ts index 2d645111ce..daa27f596f 100644 --- a/ui/actions/integrations/saml.ts +++ b/ui/actions/integrations/saml.ts @@ -1,20 +1,9 @@ "use server"; import { revalidatePath } from "next/cache"; -import { z } from "zod"; import { apiBaseUrl, getAuthHeaders, parseStringify } from "@/lib/helper"; - -const samlConfigFormSchema = z.object({ - email_domain: z - .string() - .trim() - .min(1, { message: "Email domain is required" }), - metadata_xml: z - .string() - .trim() - .min(1, { message: "Metadata XML is required" }), -}); +import { samlConfigFormSchema } from "@/types/formSchemas"; export const createSamlConfig = async (_prevState: any, formData: FormData) => { const headers = await getAuthHeaders({ contentType: true }); @@ -156,6 +145,39 @@ export const getSamlConfig = async () => { } }; +export const deleteSamlConfig = async (id: string) => { + const headers = await getAuthHeaders({ contentType: true }); + + try { + const url = new URL(`${apiBaseUrl}/saml-config/${id}`); + const response = await fetch(url.toString(), { + method: "DELETE", + headers, + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error( + errorData.errors?.[0]?.detail || + `Failed to delete SAML config: ${response.statusText}`, + ); + } + + revalidatePath("/integrations"); + return { success: "SAML configuration deleted successfully!" }; + } catch (error) { + console.error("Error deleting SAML config:", error); + return { + errors: { + general: + error instanceof Error + ? error.message + : "Error deleting SAML configuration. Please try again.", + }, + }; + } +}; + export const initiateSamlAuth = async (email: string) => { try { const response = await fetch(`${apiBaseUrl}/auth/saml/initiate/`, { diff --git a/ui/app/(prowler)/profile/page.tsx b/ui/app/(prowler)/profile/page.tsx index 661abdc243..74108355f9 100644 --- a/ui/app/(prowler)/profile/page.tsx +++ b/ui/app/(prowler)/profile/page.tsx @@ -85,7 +85,7 @@ const SSRDataUser = async () => {
- +
); diff --git a/ui/components/integrations/forms/saml-config-form.tsx b/ui/components/integrations/forms/saml-config-form.tsx index c7a6cc54a1..31c63bf4e0 100644 --- a/ui/components/integrations/forms/saml-config-form.tsx +++ b/ui/components/integrations/forms/saml-config-form.tsx @@ -1,5 +1,6 @@ "use client"; +import Link from "next/link"; import { Dispatch, SetStateAction, useEffect, useRef, useState } from "react"; import { useFormState } from "react-dom"; @@ -13,16 +14,18 @@ import { apiBaseUrl } from "@/lib"; export const SamlConfigForm = ({ setIsOpen, - id, + samlConfig, }: { setIsOpen: Dispatch>; - id: string; + samlConfig?: any; }) => { const [state, formAction, isPending] = useFormState( - id ? updateSamlConfig : createSamlConfig, + samlConfig?.id ? updateSamlConfig : createSamlConfig, null, ); - const [emailDomain, setEmailDomain] = useState(""); + const [emailDomain, setEmailDomain] = useState( + samlConfig?.attributes?.email_domain || "", + ); const [uploadedFile, setUploadedFile] = useState<{ name: string; uploaded: boolean; @@ -126,7 +129,7 @@ export const SamlConfigForm = ({ return (
- + -
- - Name ID Format: - - -
-
Supported Assertion Attributes: @@ -195,7 +187,11 @@ export const SamlConfigForm = ({ Note: The userType attribute will be used to assign the user's role. If the role does not exist, one will be created with minimal permissions. You can assign permissions to - roles on the Roles page. + roles on the{" "} + + Roles + {" "} + page.

@@ -253,7 +249,10 @@ export const SamlConfigForm = ({ {state?.errors?.metadata_xml} - + ); }; diff --git a/ui/components/integrations/saml-integration-card.tsx b/ui/components/integrations/saml-integration-card.tsx index e1f4cb66f0..ae3eb6f60d 100644 --- a/ui/components/integrations/saml-integration-card.tsx +++ b/ui/components/integrations/saml-integration-card.tsx @@ -2,15 +2,50 @@ import { Card, CardBody, CardHeader } from "@nextui-org/react"; import { Link } from "@nextui-org/react"; -import { CheckIcon } from "lucide-react"; +import { CheckIcon, Trash2Icon } from "lucide-react"; import { useState } from "react"; +import { deleteSamlConfig } from "@/actions/integrations"; +import { useToast } from "@/components/ui"; import { CustomAlertModal, CustomButton } from "@/components/ui/custom"; import { SamlConfigForm } from "./forms"; -export const SamlIntegrationCard = ({ id }: { id: string }) => { +export const SamlIntegrationCard = ({ samlConfig }: { samlConfig?: any }) => { const [isSamlModalOpen, setIsSamlModalOpen] = useState(false); + const [isDeleting, setIsDeleting] = useState(false); + const { toast } = useToast(); + const id = samlConfig?.id; + + const handleRemoveSaml = async () => { + if (!id) return; + + setIsDeleting(true); + try { + const result = await deleteSamlConfig(id); + + if (result.success) { + toast({ + title: "SAML configuration removed", + description: result.success, + }); + } else if (result.errors?.general) { + toast({ + variant: "destructive", + title: "Error removing SAML configuration", + description: result.errors.general, + }); + } + } catch (error) { + toast({ + variant: "destructive", + title: "Error", + description: "Failed to remove SAML configuration. Please try again.", + }); + } finally { + setIsDeleting(false); + } + }; return ( <> @@ -19,7 +54,10 @@ export const SamlIntegrationCard = ({ id }: { id: string }) => { onOpenChange={setIsSamlModalOpen} title="Configure SAML SSO" > - + @@ -56,14 +94,29 @@ export const SamlIntegrationCard = ({ id }: { id: string }) => { {id ? "Enabled" : "Disabled"} - setIsSamlModalOpen(true)} - > - {id ? "Update" : "Enable"} - +
+ setIsSamlModalOpen(true)} + > + {id ? "Update" : "Enable"} + + {id && ( + : null} + onPress={handleRemoveSaml} + > + Remove + + )} +
diff --git a/ui/types/formSchemas.ts b/ui/types/formSchemas.ts index 32a58f9336..d190def6c3 100644 --- a/ui/types/formSchemas.ts +++ b/ui/types/formSchemas.ts @@ -309,3 +309,14 @@ export const editUserFormSchema = () => userId: z.string(), role: z.string().optional(), }); + +export const samlConfigFormSchema = z.object({ + email_domain: z + .string() + .trim() + .min(1, { message: "Email domain is required" }), + metadata_xml: z + .string() + .trim() + .min(1, { message: "Metadata XML is required" }), +});