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 (
);
};
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" }),
+});