From 0b9969a7234a9565082ddcb97cb9183d4fb0e40e Mon Sep 17 00:00:00 2001 From: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com> Date: Mon, 20 Oct 2025 13:51:11 +0200 Subject: [PATCH] feat: update M365 credentials form (#8929) Co-authored-by: HugoPBrito --- ui/CHANGELOG.md | 1 + .../add-credentials/page.tsx | 2 + .../providers/credentials-update-info.tsx | 4 + .../workflow/forms/base-credentials-form.tsx | 38 ++++-- .../m365/credentials-type/index.ts | 2 + .../m365-certificate-credentials-form.tsx | 72 ++++++++++++ .../m365-client-secret-credentials-form.tsx | 58 ++++++++++ .../select-credentials-type/m365/index.ts | 5 + ...o-group-m365-via-credentials-type-form.tsx | 72 ++++++++++++ .../m365/select-via-m365.tsx | 38 ++++++ .../workflow/forms/via-credentials/index.ts | 1 - .../via-credentials/m365-credentials-form.tsx | 109 ------------------ ui/hooks/use-credentials-form.ts | 37 ++++-- .../build-crendentials.ts | 17 ++- .../provider-credential-fields.ts | 2 + ui/lib/provider-helpers.ts | 12 +- ui/types/components.ts | 15 ++- ui/types/formSchemas.ts | 56 ++++----- 18 files changed, 382 insertions(+), 159 deletions(-) create mode 100644 ui/components/providers/workflow/forms/select-credentials-type/m365/credentials-type/index.ts create mode 100644 ui/components/providers/workflow/forms/select-credentials-type/m365/credentials-type/m365-certificate-credentials-form.tsx create mode 100644 ui/components/providers/workflow/forms/select-credentials-type/m365/credentials-type/m365-client-secret-credentials-form.tsx create mode 100644 ui/components/providers/workflow/forms/select-credentials-type/m365/index.ts create mode 100644 ui/components/providers/workflow/forms/select-credentials-type/m365/radio-group-m365-via-credentials-type-form.tsx create mode 100644 ui/components/providers/workflow/forms/select-credentials-type/m365/select-via-m365.tsx delete mode 100644 ui/components/providers/workflow/forms/via-credentials/m365-credentials-form.tsx diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md index 72c660c788..9f0174f4ba 100644 --- a/ui/CHANGELOG.md +++ b/ui/CHANGELOG.md @@ -16,6 +16,7 @@ All notable changes to the **Prowler UI** are documented in this file. - API key management in user profile [(#8308)](https://github.com/prowler-cloud/prowler/pull/8308) - Refresh access token error handling [(#8864)](https://github.com/prowler-cloud/prowler/pull/8864) - Support Common Cloud Controls for AWS, Azure and GCP [(#8000)](https://github.com/prowler-cloud/prowler/pull/8000) +- New M365 credentials certificate authentication method [(#8929)](https://github.com/prowler-cloud/prowler/pull/8929) ### 🔄 Changed diff --git a/ui/app/(prowler)/providers/(set-up-provider)/add-credentials/page.tsx b/ui/app/(prowler)/providers/(set-up-provider)/add-credentials/page.tsx index 1c039886b9..3490cdb792 100644 --- a/ui/app/(prowler)/providers/(set-up-provider)/add-credentials/page.tsx +++ b/ui/app/(prowler)/providers/(set-up-provider)/add-credentials/page.tsx @@ -10,6 +10,7 @@ import { SelectViaGCP, } from "@/components/providers/workflow/forms/select-credentials-type/gcp"; import { SelectViaGitHub } from "@/components/providers/workflow/forms/select-credentials-type/github"; +import { SelectViaM365 } from "@/components/providers/workflow/forms/select-credentials-type/m365"; import { getProviderFormType } from "@/lib/provider-helpers"; import { ProviderType } from "@/types/providers"; @@ -28,6 +29,7 @@ export default async function AddCredentialsPage({ searchParams }: Props) { if (providerType === "gcp") return ; if (providerType === "github") return ; + if (providerType === "m365") return ; return null; case "credentials": diff --git a/ui/components/providers/credentials-update-info.tsx b/ui/components/providers/credentials-update-info.tsx index 0fe75f0cb2..3fb13f97a7 100644 --- a/ui/components/providers/credentials-update-info.tsx +++ b/ui/components/providers/credentials-update-info.tsx @@ -3,6 +3,7 @@ import { SelectViaAWS } from "@/components/providers/workflow/forms/select-credentials-type/aws"; import { SelectViaGCP } from "@/components/providers/workflow/forms/select-credentials-type/gcp"; import { SelectViaGitHub } from "@/components/providers/workflow/forms/select-credentials-type/github"; +import { SelectViaM365 } from "@/components/providers/workflow/forms/select-credentials-type/m365"; import { ProviderType } from "@/types/providers"; interface UpdateCredentialsInfoProps { @@ -24,6 +25,9 @@ export const CredentialsUpdateInfo = ({ if (providerType === "github") { return ; } + if (providerType === "m365") { + return ; + } return null; }; diff --git a/ui/components/providers/workflow/forms/base-credentials-form.tsx b/ui/components/providers/workflow/forms/base-credentials-form.tsx index 9c66103e1d..a50cfdeb74 100644 --- a/ui/components/providers/workflow/forms/base-credentials-form.tsx +++ b/ui/components/providers/workflow/forms/base-credentials-form.tsx @@ -17,7 +17,8 @@ import { GCPDefaultCredentials, GCPServiceAccountKey, KubernetesCredentials, - M365Credentials, + M365CertificateCredentials, + M365ClientSecretCredentials, ProviderType, } from "@/types"; @@ -26,10 +27,13 @@ import { AWSStaticCredentialsForm } from "./select-credentials-type/aws/credenti import { AWSRoleCredentialsForm } from "./select-credentials-type/aws/credentials-type/aws-role-credentials-form"; import { GCPDefaultCredentialsForm } from "./select-credentials-type/gcp/credentials-type"; import { GCPServiceAccountKeyForm } from "./select-credentials-type/gcp/credentials-type/gcp-service-account-key-form"; +import { + M365CertificateCredentialsForm, + M365ClientSecretCredentialsForm, +} from "./select-credentials-type/m365"; import { AzureCredentialsForm } from "./via-credentials/azure-credentials-form"; import { GitHubCredentialsForm } from "./via-credentials/github-credentials-form"; import { KubernetesCredentialsForm } from "./via-credentials/k8s-credentials-form"; -import { M365CredentialsForm } from "./via-credentials/m365-credentials-form"; type BaseCredentialsFormProps = { providerType: ProviderType; @@ -103,11 +107,22 @@ export const BaseCredentialsForm = ({ control={form.control as unknown as Control} /> )} - {providerType === "m365" && ( - } - /> - )} + {providerType === "m365" && + searchParamsObj.get("via") === "app_client_secret" && ( + + } + /> + )} + {providerType === "m365" && + searchParamsObj.get("via") === "app_certificate" && ( + + } + /> + )} {providerType === "gcp" && searchParamsObj.get("via") === "service-account" && ( } + onPress={(e) => { + const formElement = e.target as HTMLElement; + const form = formElement.closest("form"); + if (form) { + form.dispatchEvent( + new Event("submit", { bubbles: true, cancelable: true }), + ); + } + }} > {isLoading ? <>Loading : {submitButtonText}} diff --git a/ui/components/providers/workflow/forms/select-credentials-type/m365/credentials-type/index.ts b/ui/components/providers/workflow/forms/select-credentials-type/m365/credentials-type/index.ts new file mode 100644 index 0000000000..91d6766bac --- /dev/null +++ b/ui/components/providers/workflow/forms/select-credentials-type/m365/credentials-type/index.ts @@ -0,0 +1,2 @@ +export { M365CertificateCredentialsForm } from "./m365-certificate-credentials-form"; +export { M365ClientSecretCredentialsForm } from "./m365-client-secret-credentials-form"; diff --git a/ui/components/providers/workflow/forms/select-credentials-type/m365/credentials-type/m365-certificate-credentials-form.tsx b/ui/components/providers/workflow/forms/select-credentials-type/m365/credentials-type/m365-certificate-credentials-form.tsx new file mode 100644 index 0000000000..03dd5a72e0 --- /dev/null +++ b/ui/components/providers/workflow/forms/select-credentials-type/m365/credentials-type/m365-certificate-credentials-form.tsx @@ -0,0 +1,72 @@ +"use client"; + +import { Control } from "react-hook-form"; + +import { CustomInput, CustomTextarea } from "@/components/ui/custom"; +import { CustomLink } from "@/components/ui/custom/custom-link"; +import { M365CertificateCredentials } from "@/types"; + +export const M365CertificateCredentialsForm = ({ + control, +}: { + control: Control; +}) => { + return ( + <> +
+
+ App Certificate Credentials +
+
+ Please provide your Microsoft 365 application credentials with + certificate authentication. +
+
+ + + +

+ The certificate content must be base64 encoded from an unsigned + certificate. For detailed instructions on how to generate and encode + your certificate, please refer to the{" "} + + certificate generation guide + + . +

+ + ); +}; diff --git a/ui/components/providers/workflow/forms/select-credentials-type/m365/credentials-type/m365-client-secret-credentials-form.tsx b/ui/components/providers/workflow/forms/select-credentials-type/m365/credentials-type/m365-client-secret-credentials-form.tsx new file mode 100644 index 0000000000..3312fb536a --- /dev/null +++ b/ui/components/providers/workflow/forms/select-credentials-type/m365/credentials-type/m365-client-secret-credentials-form.tsx @@ -0,0 +1,58 @@ +"use client"; + +import { Control } from "react-hook-form"; + +import { CustomInput } from "@/components/ui/custom"; +import { M365ClientSecretCredentials } from "@/types"; + +export const M365ClientSecretCredentialsForm = ({ + control, +}: { + control: Control; +}) => { + return ( + <> +
+
+ App Client Secret Credentials +
+
+ Please provide your Microsoft 365 application credentials. +
+
+ + + + + ); +}; diff --git a/ui/components/providers/workflow/forms/select-credentials-type/m365/index.ts b/ui/components/providers/workflow/forms/select-credentials-type/m365/index.ts new file mode 100644 index 0000000000..84610f510a --- /dev/null +++ b/ui/components/providers/workflow/forms/select-credentials-type/m365/index.ts @@ -0,0 +1,5 @@ +export { + M365CertificateCredentialsForm, + M365ClientSecretCredentialsForm, +} from "./credentials-type"; +export { SelectViaM365 } from "./select-via-m365"; diff --git a/ui/components/providers/workflow/forms/select-credentials-type/m365/radio-group-m365-via-credentials-type-form.tsx b/ui/components/providers/workflow/forms/select-credentials-type/m365/radio-group-m365-via-credentials-type-form.tsx new file mode 100644 index 0000000000..2f5599363b --- /dev/null +++ b/ui/components/providers/workflow/forms/select-credentials-type/m365/radio-group-m365-via-credentials-type-form.tsx @@ -0,0 +1,72 @@ +"use client"; + +import { RadioGroup } from "@heroui/radio"; +import React from "react"; +import { Control, Controller } from "react-hook-form"; + +import { CustomRadio } from "@/components/ui/custom"; +import { FormMessage } from "@/components/ui/form"; + +type RadioGroupM365ViaCredentialsFormProps = { + control: Control; + isInvalid: boolean; + errorMessage?: string; + onChange?: (value: string) => void; +}; + +export const RadioGroupM365ViaCredentialsTypeForm = ({ + control, + isInvalid, + errorMessage, + onChange, +}: RadioGroupM365ViaCredentialsFormProps) => { + return ( + ( + <> + { + field.onChange(value); + if (onChange) { + onChange(value); + } + }} + > +
+ + Select Authentication Method + + +
+ App Client Secret Credentials +
+
+ +
+ App Certificate Credentials +
+
+
+
+ {errorMessage && ( + + {errorMessage} + + )} + + )} + /> + ); +}; diff --git a/ui/components/providers/workflow/forms/select-credentials-type/m365/select-via-m365.tsx b/ui/components/providers/workflow/forms/select-credentials-type/m365/select-via-m365.tsx new file mode 100644 index 0000000000..020e4ed0e1 --- /dev/null +++ b/ui/components/providers/workflow/forms/select-credentials-type/m365/select-via-m365.tsx @@ -0,0 +1,38 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { useForm } from "react-hook-form"; + +import { Form } from "@/components/ui/form"; + +import { RadioGroupM365ViaCredentialsTypeForm } from "./radio-group-m365-via-credentials-type-form"; + +interface SelectViaM365Props { + initialVia?: string; +} + +export const SelectViaM365 = ({ initialVia }: SelectViaM365Props) => { + const router = useRouter(); + const form = useForm({ + defaultValues: { + m365CredentialsType: initialVia || "", + }, + }); + + const handleSelectionChange = (value: string) => { + const url = new URL(window.location.href); + url.searchParams.set("via", value); + router.push(url.toString()); + }; + + return ( +
+ + + ); +}; diff --git a/ui/components/providers/workflow/forms/via-credentials/index.ts b/ui/components/providers/workflow/forms/via-credentials/index.ts index d020b9715f..982e5ca701 100644 --- a/ui/components/providers/workflow/forms/via-credentials/index.ts +++ b/ui/components/providers/workflow/forms/via-credentials/index.ts @@ -1,4 +1,3 @@ export * from "./azure-credentials-form"; export * from "./github-credentials-form"; export * from "./k8s-credentials-form"; -export * from "./m365-credentials-form"; diff --git a/ui/components/providers/workflow/forms/via-credentials/m365-credentials-form.tsx b/ui/components/providers/workflow/forms/via-credentials/m365-credentials-form.tsx deleted file mode 100644 index 1659a503e8..0000000000 --- a/ui/components/providers/workflow/forms/via-credentials/m365-credentials-form.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { Control } from "react-hook-form"; - -import { InfoIcon } from "@/components/icons"; -import { CustomInput } from "@/components/ui/custom"; -import { CustomLink } from "@/components/ui/custom/custom-link"; -import { M365Credentials } from "@/types"; - -export const M365CredentialsForm = ({ - control, -}: { - control: Control; -}) => { - return ( - <> -
-
- Connect via Credentials -
-
- Please provide the information for your Microsoft 365 credentials. -
-
- - - -

- {" "} - User and password authentication is being deprecated due to - Microsoft's on-going MFA enforcement across all tenants (see{" "} - - Microsoft docs - - ). -

- -
- -

- By October 2025, MFA will be mandatory. -

-
-

- Due to that change, you must only{" "} - - use application authentication - {" "} - to maintain all Prowler M365 scan capabilities. -

- - - - ); -}; diff --git a/ui/hooks/use-credentials-form.ts b/ui/hooks/use-credentials-form.ts index 268d714d71..2690b9307a 100644 --- a/ui/hooks/use-credentials-form.ts +++ b/ui/hooks/use-credentials-form.ts @@ -40,8 +40,8 @@ export const useCredentialsForm = ({ if (providerType === "gcp" && via === "service-account") { return addCredentialsServiceAccountFormSchema(providerType); } - // For GitHub, we need to pass the via parameter to determine which fields are required - if (providerType === "github") { + // For GitHub and M365, we need to pass the via parameter to determine which fields are required + if (providerType === "github" || providerType === "m365") { return addCredentialsFormSchema(providerType, via); } return addCredentialsFormSchema(providerType); @@ -99,13 +99,27 @@ export const useCredentialsForm = ({ [ProviderCredentialFields.TENANT_ID]: "", }; case "m365": + // M365 credentials based on via parameter + if (via === "app_client_secret") { + return { + ...baseDefaults, + [ProviderCredentialFields.CLIENT_ID]: "", + [ProviderCredentialFields.CLIENT_SECRET]: "", + [ProviderCredentialFields.TENANT_ID]: "", + }; + } + if (via === "app_certificate") { + return { + ...baseDefaults, + [ProviderCredentialFields.CLIENT_ID]: "", + [ProviderCredentialFields.CERTIFICATE_CONTENT]: "", + [ProviderCredentialFields.TENANT_ID]: "", + }; + } return { ...baseDefaults, [ProviderCredentialFields.CLIENT_ID]: "", - [ProviderCredentialFields.CLIENT_SECRET]: "", [ProviderCredentialFields.TENANT_ID]: "", - [ProviderCredentialFields.USER]: "", - [ProviderCredentialFields.PASSWORD]: "", }; case "gcp": return { @@ -146,9 +160,14 @@ export const useCredentialsForm = ({ } }; + const defaultValues = getDefaultValues(); + const form = useForm({ resolver: zodResolver(formSchema), - defaultValues: getDefaultValues(), + defaultValues: defaultValues, + mode: "onSubmit", + reValidateMode: "onChange", + criteriaMode: "all", // Show all errors for each field }); const { handleServerResponse } = useFormServerErrors( @@ -169,6 +188,7 @@ export const useCredentialsForm = ({ // Filter out empty values first, then append all remaining values const filteredValues = filterEmptyValues(values); + Object.entries(filteredValues).forEach(([key, value]) => { formData.append(key, value); }); @@ -181,9 +201,12 @@ export const useCredentialsForm = ({ } }; + const { isSubmitting, errors } = form.formState; + return { form, - isLoading: form.formState.isSubmitting, + isLoading: isSubmitting, + errors, handleSubmit, handleBackStep, searchParamsObj, diff --git a/ui/lib/provider-credentials/build-crendentials.ts b/ui/lib/provider-credentials/build-crendentials.ts index 475702db0c..2a0abd31c7 100644 --- a/ui/lib/provider-credentials/build-crendentials.ts +++ b/ui/lib/provider-credentials/build-crendentials.ts @@ -80,14 +80,21 @@ export const buildAzureSecret = (formData: FormData) => { export const buildM365Secret = (formData: FormData) => { const secret = { - ...buildAzureSecret(formData), - [ProviderCredentialFields.USER]: getFormValue( + [ProviderCredentialFields.CLIENT_ID]: getFormValue( formData, - ProviderCredentialFields.USER, + ProviderCredentialFields.CLIENT_ID, ), - [ProviderCredentialFields.PASSWORD]: getFormValue( + [ProviderCredentialFields.TENANT_ID]: getFormValue( formData, - ProviderCredentialFields.PASSWORD, + ProviderCredentialFields.TENANT_ID, + ), + [ProviderCredentialFields.CLIENT_SECRET]: getFormValue( + formData, + ProviderCredentialFields.CLIENT_SECRET, + ), + [ProviderCredentialFields.CERTIFICATE_CONTENT]: getFormValue( + formData, + ProviderCredentialFields.CERTIFICATE_CONTENT, ), }; return filterEmptyValues(secret); diff --git a/ui/lib/provider-credentials/provider-credential-fields.ts b/ui/lib/provider-credentials/provider-credential-fields.ts index 32dc184292..635c74bf44 100644 --- a/ui/lib/provider-credentials/provider-credential-fields.ts +++ b/ui/lib/provider-credentials/provider-credential-fields.ts @@ -29,6 +29,7 @@ export const ProviderCredentialFields = { TENANT_ID: "tenant_id", USER: "user", PASSWORD: "password", + CERTIFICATE_CONTENT: "certificate_content", // GCP fields REFRESH_TOKEN: "refresh_token", @@ -70,6 +71,7 @@ export const ErrorPointers = { OAUTH_APP_TOKEN: "/data/attributes/secret/oauth_app_token", GITHUB_APP_ID: "/data/attributes/secret/github_app_id", GITHUB_APP_KEY: "/data/attributes/secret/github_app_key_content", + CERTIFICATE_CONTENT: "/data/attributes/secret/certificate_content", } as const; export type ErrorPointer = (typeof ErrorPointers)[keyof typeof ErrorPointers]; diff --git a/ui/lib/provider-helpers.ts b/ui/lib/provider-helpers.ts index becd9bc4f2..1bcf5d80bb 100644 --- a/ui/lib/provider-helpers.ts +++ b/ui/lib/provider-helpers.ts @@ -53,7 +53,7 @@ export const getProviderFormType = ( via?: string, ): ProviderFormType => { // Providers that need credential type selection - const needsSelector = ["aws", "gcp", "github"].includes(providerType); + const needsSelector = ["aws", "gcp", "github", "m365"].includes(providerType); // Show selector if no via parameter and provider needs it if (needsSelector && !via) { @@ -80,6 +80,14 @@ export const getProviderFormType = ( return "credentials"; } + // M365 credential types + if ( + providerType === "m365" && + ["app_client_secret", "app_certificate"].includes(via || "") + ) { + return "credentials"; + } + // Other providers go directly to credentials form if (!needsSelector) { return "credentials"; @@ -99,6 +107,8 @@ export const requiresBackButton = (via?: string | null): boolean => { "personal_access_token", "oauth_app", "github_app", + "app_client_secret", + "app_certificate", ]; return validViaTypes.includes(via); diff --git a/ui/types/components.ts b/ui/types/components.ts index 27de816762..6485f7d0db 100644 --- a/ui/types/components.ts +++ b/ui/types/components.ts @@ -213,15 +213,24 @@ export type AzureCredentials = { [ProviderCredentialFields.PROVIDER_ID]: string; }; -export type M365Credentials = { +export type M365ClientSecretCredentials = { [ProviderCredentialFields.CLIENT_ID]: string; [ProviderCredentialFields.CLIENT_SECRET]: string; [ProviderCredentialFields.TENANT_ID]: string; - [ProviderCredentialFields.USER]?: string; - [ProviderCredentialFields.PASSWORD]?: string; [ProviderCredentialFields.PROVIDER_ID]: string; }; +export type M365CertificateCredentials = { + [ProviderCredentialFields.CLIENT_ID]: string; + [ProviderCredentialFields.CERTIFICATE_CONTENT]: string; + [ProviderCredentialFields.TENANT_ID]: string; + [ProviderCredentialFields.PROVIDER_ID]: string; +}; + +export type M365Credentials = + | M365ClientSecretCredentials + | M365CertificateCredentials; + export type GCPDefaultCredentials = { client_id: string; client_secret: string; diff --git a/ui/types/formSchemas.ts b/ui/types/formSchemas.ts index 10900a3f26..d6421ee16d 100644 --- a/ui/types/formSchemas.ts +++ b/ui/types/formSchemas.ts @@ -168,12 +168,13 @@ export const addCredentialsFormSchema = ( .min(1, "Client ID is required"), [ProviderCredentialFields.CLIENT_SECRET]: z .string() - .min(1, "Client Secret is required"), + .optional(), + [ProviderCredentialFields.CERTIFICATE_CONTENT]: z + .string() + .optional(), [ProviderCredentialFields.TENANT_ID]: z .string() .min(1, "Tenant ID is required"), - [ProviderCredentialFields.USER]: z.string().optional(), - [ProviderCredentialFields.PASSWORD]: z.string().optional(), } : providerType === "github" ? { @@ -194,23 +195,26 @@ export const addCredentialsFormSchema = ( }) .superRefine((data: Record, ctx) => { if (providerType === "m365") { - const hasUser = !!data[ProviderCredentialFields.USER]; - const hasPassword = !!data[ProviderCredentialFields.PASSWORD]; - - if (hasUser && !hasPassword) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: "If you provide a user, you must also provide a password", - path: [ProviderCredentialFields.PASSWORD], - }); - } - - if (hasPassword && !hasUser) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: "If you provide a password, you must also provide a user", - path: [ProviderCredentialFields.USER], - }); + // Validate based on the via parameter + if (via === "app_client_secret") { + const clientSecret = data[ProviderCredentialFields.CLIENT_SECRET]; + if (!clientSecret || clientSecret.trim() === "") { + ctx.addIssue({ + code: "custom", + message: "Client Secret is required", + path: [ProviderCredentialFields.CLIENT_SECRET], + }); + } + } else if (via === "app_certificate") { + const certificateContent = + data[ProviderCredentialFields.CERTIFICATE_CONTENT]; + if (!certificateContent || certificateContent.trim() === "") { + ctx.addIssue({ + code: "custom", + message: "Certificate Content is required", + path: [ProviderCredentialFields.CERTIFICATE_CONTENT], + }); + } } } @@ -219,7 +223,7 @@ export const addCredentialsFormSchema = ( if (via === "personal_access_token") { if (!data[ProviderCredentialFields.PERSONAL_ACCESS_TOKEN]) { ctx.addIssue({ - code: z.ZodIssueCode.custom, + code: "custom", message: "Personal Access Token is required", path: [ProviderCredentialFields.PERSONAL_ACCESS_TOKEN], }); @@ -227,7 +231,7 @@ export const addCredentialsFormSchema = ( } else if (via === "oauth_app") { if (!data[ProviderCredentialFields.OAUTH_APP_TOKEN]) { ctx.addIssue({ - code: z.ZodIssueCode.custom, + code: "custom", message: "OAuth App Token is required", path: [ProviderCredentialFields.OAUTH_APP_TOKEN], }); @@ -235,14 +239,14 @@ export const addCredentialsFormSchema = ( } else if (via === "github_app") { if (!data[ProviderCredentialFields.GITHUB_APP_ID]) { ctx.addIssue({ - code: z.ZodIssueCode.custom, + code: "custom", message: "GitHub App ID is required", path: [ProviderCredentialFields.GITHUB_APP_ID], }); } if (!data[ProviderCredentialFields.GITHUB_APP_KEY]) { ctx.addIssue({ - code: z.ZodIssueCode.custom, + code: "custom", message: "GitHub App Private Key is required", path: [ProviderCredentialFields.GITHUB_APP_KEY], }); @@ -390,7 +394,7 @@ export const mutedFindingsConfigFormSchema = z.object({ const yamlValidation = validateYaml(val); if (!yamlValidation.isValid) { ctx.addIssue({ - code: z.ZodIssueCode.custom, + code: "custom", message: `Invalid YAML format: ${yamlValidation.error}`, }); return; @@ -399,7 +403,7 @@ export const mutedFindingsConfigFormSchema = z.object({ const mutelistValidation = validateMutelistYaml(val); if (!mutelistValidation.isValid) { ctx.addIssue({ - code: z.ZodIssueCode.custom, + code: "custom", message: `Invalid mutelist structure: ${mutelistValidation.error}`, }); }