mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-23 20:42:02 +00:00
feat: add select auth method form
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
AddViaServiceAccountForm,
|
||||
SelectViaGCP,
|
||||
} from "@/components/providers/workflow/forms/select-credentials-type/gcp";
|
||||
import { SelectViaM365 } from "@/components/providers/workflow/forms/select-credentials-type/m365/select-via-m365";
|
||||
import { ProviderType } from "@/types/providers";
|
||||
|
||||
interface Props {
|
||||
@@ -26,9 +27,15 @@ export default function AddCredentialsPage({ searchParams }: Props) {
|
||||
<SelectViaGCP initialVia={searchParams.via} />
|
||||
)}
|
||||
|
||||
{searchParams.type === "m365" && !searchParams.via && (
|
||||
<SelectViaM365 initialVia={searchParams.via} />
|
||||
)}
|
||||
|
||||
{((searchParams.type === "aws" && searchParams.via === "credentials") ||
|
||||
(searchParams.type === "gcp" && searchParams.via === "credentials") ||
|
||||
(searchParams.type !== "aws" && searchParams.type !== "gcp")) && (
|
||||
(searchParams.type === "m365" && searchParams.via === "credentials") ||
|
||||
(searchParams.type === "m365" && searchParams.via === "service-principal-user") ||
|
||||
(searchParams.type !== "aws" && searchParams.type !== "gcp" && searchParams.type !== "m365")) && (
|
||||
<AddViaCredentialsForm searchParams={searchParams} />
|
||||
)}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ interface Props {
|
||||
export default function UpdateCredentialsPage({ searchParams }: Props) {
|
||||
return (
|
||||
<>
|
||||
{(searchParams.type === "aws" || searchParams.type === "gcp") &&
|
||||
{(searchParams.type === "aws" || searchParams.type === "gcp" || searchParams.type === "m365") &&
|
||||
!searchParams.via && (
|
||||
<CredentialsUpdateInfo
|
||||
providerType={searchParams.type}
|
||||
@@ -30,7 +30,9 @@ export default function UpdateCredentialsPage({ searchParams }: Props) {
|
||||
|
||||
{((searchParams.type === "aws" && searchParams.via === "credentials") ||
|
||||
(searchParams.type === "gcp" && searchParams.via === "credentials") ||
|
||||
(searchParams.type !== "aws" && searchParams.type !== "gcp")) && (
|
||||
(searchParams.type === "m365" && searchParams.via === "credentials") ||
|
||||
(searchParams.type === "m365" && searchParams.via === "service-principal-user") ||
|
||||
(searchParams.type !== "aws" && searchParams.type !== "gcp" && searchParams.type !== "m365")) && (
|
||||
<UpdateViaCredentialsForm searchParams={searchParams} />
|
||||
)}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { SelectViaAWS } from "@/components/providers/workflow/forms/select-credentials-type/aws";
|
||||
import { SelectViaGCP } from "@/components/providers/workflow/forms/select-credentials-type/gcp";
|
||||
import { SelectViaM365 } from "@/components/providers/workflow/forms/select-credentials-type/m365/select-via-m365";
|
||||
import { ProviderType } from "@/types/providers";
|
||||
|
||||
interface UpdateCredentialsInfoProps {
|
||||
@@ -20,6 +21,9 @@ export const CredentialsUpdateInfo = ({
|
||||
if (providerType === "gcp") {
|
||||
return <SelectViaGCP initialVia={initialVia} />;
|
||||
}
|
||||
if (providerType === "m365") {
|
||||
return <SelectViaM365 initialVia={initialVia} />;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
@@ -26,7 +26,8 @@ import { GCPDefaultCredentialsForm } from "./select-credentials-type/gcp/credent
|
||||
import { GCPServiceAccountKeyForm } from "./select-credentials-type/gcp/credentials-type/gcp-service-account-key-form";
|
||||
import { AzureCredentialsForm } from "./via-credentials/azure-credentials-form";
|
||||
import { KubernetesCredentialsForm } from "./via-credentials/k8s-credentials-form";
|
||||
import { M365CredentialsForm } from "./via-credentials/m365-credentials-form";
|
||||
import { M365ServicePrincipalForm } from "./select-credentials-type/m365/credentials-type";
|
||||
import { M365ServicePrincipalUserForm } from "./select-credentials-type/m365/credentials-type/m365-service-principal-user-form";
|
||||
|
||||
type BaseCredentialsFormProps = {
|
||||
providerType: ProviderType;
|
||||
@@ -97,8 +98,13 @@ export const BaseCredentialsForm = ({
|
||||
control={form.control as unknown as Control<AzureCredentials>}
|
||||
/>
|
||||
)}
|
||||
{providerType === "m365" && (
|
||||
<M365CredentialsForm
|
||||
{providerType === "m365" && searchParamsObj.get("via") === "service-principal-user" && (
|
||||
<M365ServicePrincipalUserForm
|
||||
control={form.control as unknown as Control<M365Credentials>}
|
||||
/>
|
||||
)}
|
||||
{providerType === "m365" && searchParamsObj.get("via") !== "service-principal-user" && (
|
||||
<M365ServicePrincipalForm
|
||||
control={form.control as unknown as Control<M365Credentials>}
|
||||
/>
|
||||
)}
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export { M365ServicePrincipalForm } from "./m365-service-principal-form";
|
||||
export { M365ServicePrincipalUserForm } from "./m365-service-principal-user-form";
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
import Link from "next/link";
|
||||
import { Control, UseFormSetValue, useWatch } from "react-hook-form";
|
||||
|
||||
import { CustomInput } from "@/components/ui/custom";
|
||||
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
|
||||
import { M365Credentials } from "@/types";
|
||||
|
||||
export const M365ServicePrincipalForm = ({
|
||||
control,
|
||||
setValue,
|
||||
}: {
|
||||
control: Control<M365Credentials>;
|
||||
setValue: UseFormSetValue<M365Credentials>;
|
||||
externalId: string;
|
||||
}) => {
|
||||
const credentialsType = useWatch({
|
||||
control,
|
||||
name: ProviderCredentialFields.CREDENTIALS_TYPE,
|
||||
defaultValue: "m365-service-principal",
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col">
|
||||
<div className="text-md font-bold leading-9 text-default-foreground">
|
||||
Connect using Application/Service Principal
|
||||
</div>
|
||||
<div className="text-sm text-default-500">
|
||||
Please provide the information for your Microsoft 365 Service Principal.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span className="text-xs font-bold text-default-500">Authentication</span>
|
||||
|
||||
<Select
|
||||
name={ProviderCredentialFields.CREDENTIALS_TYPE}
|
||||
label="Authentication Method"
|
||||
placeholder="Select credentials type"
|
||||
defaultSelectedKeys={["m365-service-principal"]}
|
||||
className="mb-4"
|
||||
variant="bordered"
|
||||
onSelectionChange={(keys) =>
|
||||
setValue(
|
||||
ProviderCredentialFields.CREDENTIALS_TYPE,
|
||||
Array.from(keys)[0] as "m365-service-principal" | "m365-user-credentials",
|
||||
)
|
||||
}
|
||||
>
|
||||
<SelectItem key="m365-service-principal">Application/Service Principal</SelectItem>
|
||||
<SelectItem key="m365-user-credentials">User credentials</SelectItem>
|
||||
</Select>
|
||||
|
||||
{credentialsType === "m365-service-principal" && (
|
||||
<>
|
||||
<CustomInput
|
||||
control={control}
|
||||
name={ProviderCredentialFields.CLIENT_ID}
|
||||
type="text"
|
||||
label="Client ID"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the Client ID"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={!!control._formState.errors[ProviderCredentialFields.CLIENT_ID]}
|
||||
/>
|
||||
<CustomInput
|
||||
control={control}
|
||||
name={ProviderCredentialFields.CLIENT_SECRET}
|
||||
type="password"
|
||||
label="Client Secret"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the Client Secret"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={!!control._formState.errors[ProviderCredentialFields.CLIENT_SECRET]}
|
||||
/>
|
||||
<CustomInput
|
||||
control={control}
|
||||
name={ProviderCredentialFields.TENANT_ID}
|
||||
type="text"
|
||||
label="Tenant ID"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the Tenant ID"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={!!control._formState.errors[ProviderCredentialFields.TENANT_ID]}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{credentialsType === "m365-user-credentials" && (
|
||||
<>
|
||||
<CustomInput
|
||||
control={control}
|
||||
name={ProviderCredentialFields.CLIENT_ID}
|
||||
type="text"
|
||||
label="Client ID"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the Client ID"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={!!control._formState.errors[ProviderCredentialFields.CLIENT_ID]}
|
||||
/>
|
||||
|
||||
<CustomInput
|
||||
control={control}
|
||||
name={ProviderCredentialFields.CLIENT_SECRET}
|
||||
type="password"
|
||||
label="Client Secret"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the Client Secret"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={!!control._formState.errors[ProviderCredentialFields.CLIENT_SECRET]}
|
||||
/>
|
||||
|
||||
<CustomInput
|
||||
control={control}
|
||||
name={ProviderCredentialFields.TENANT_ID}
|
||||
type="text"
|
||||
label="Tenant ID"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the Tenant ID"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={!!control._formState.errors[ProviderCredentialFields.TENANT_ID]}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
import Link from "next/link";
|
||||
import { Control } from "react-hook-form";
|
||||
|
||||
import { InfoIcon } from "@/components/icons";
|
||||
import { CustomInput } from "@/components/ui/custom";
|
||||
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
|
||||
import { M365Credentials } from "@/types";
|
||||
|
||||
export const M365ServicePrincipalUserForm = ({
|
||||
control,
|
||||
}: {
|
||||
control: Control<M365Credentials>;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col">
|
||||
<div className="text-md font-bold leading-9 text-default-foreground">
|
||||
Service Principal + User Credentials
|
||||
</div>
|
||||
<div className="text-sm text-default-500">
|
||||
Connect using Service Principal credentials combined with user authentication for PowerShell modules.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center rounded-lg border border-system-info bg-system-info-medium p-3 text-sm dark:text-default-300">
|
||||
<InfoIcon className="mr-2 inline h-4 w-4 flex-shrink-0" />
|
||||
<div className="text-xs">
|
||||
<p className="font-extrabold mb-2">This method provides:</p>
|
||||
<ul className="list-disc list-inside space-y-1">
|
||||
<li>Service Principal authentication for Microsoft Graph APIs</li>
|
||||
<li>User credentials for PowerShell modules access</li>
|
||||
<li>Complete M365 service coverage</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span className="text-xs font-bold text-default-500">Service Principal Information</span>
|
||||
|
||||
<CustomInput
|
||||
control={control}
|
||||
name={ProviderCredentialFields.CLIENT_ID}
|
||||
type="text"
|
||||
label="Client ID"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the Client ID"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={!!control._formState.errors[ProviderCredentialFields.CLIENT_ID]}
|
||||
/>
|
||||
|
||||
<CustomInput
|
||||
control={control}
|
||||
name={ProviderCredentialFields.CLIENT_SECRET}
|
||||
type="password"
|
||||
label="Client Secret"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the Client Secret"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={!!control._formState.errors[ProviderCredentialFields.CLIENT_SECRET]}
|
||||
/>
|
||||
|
||||
<CustomInput
|
||||
control={control}
|
||||
name={ProviderCredentialFields.TENANT_ID}
|
||||
type="text"
|
||||
label="Tenant ID"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the Tenant ID"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={!!control._formState.errors[ProviderCredentialFields.TENANT_ID]}
|
||||
/>
|
||||
|
||||
<span className="text-xs font-bold text-default-500">User Credentials for PowerShell</span>
|
||||
|
||||
<CustomInput
|
||||
control={control}
|
||||
name={ProviderCredentialFields.USER}
|
||||
type="text"
|
||||
label="User"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the User (e.g., user@company.onmicrosoft.com)"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={!!control._formState.errors[ProviderCredentialFields.USER]}
|
||||
/>
|
||||
|
||||
<CustomInput
|
||||
control={control}
|
||||
name={ProviderCredentialFields.PASSWORD}
|
||||
type="password"
|
||||
label="Password"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the Password"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={!!control._formState.errors[ProviderCredentialFields.PASSWORD]}
|
||||
/>
|
||||
|
||||
<div className="flex items-center rounded-lg border border-system-warning bg-system-warning-medium p-2 text-sm dark:text-default-300">
|
||||
<InfoIcon className="mr-2 inline h-4 w-4 flex-shrink-0" />
|
||||
<p className="text-xs font-extrabold">
|
||||
By September 2025, User Authentication will be deprecated.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p className="text-sm text-default-500">
|
||||
For more information on setting up authentication, see the{" "}
|
||||
<Link
|
||||
href="https://docs.prowler.com/projects/prowler-open-source/en/latest/tutorials/microsoft365/getting-started-m365/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm text-primary hover:underline"
|
||||
>
|
||||
M365 setup documentation
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
export { SelectViaM365 } from "./select-via-m365";
|
||||
export { RadioGroupM365ViaCredentialsTypeForm } from "./radio-group-m365-via-credentials-type-form";
|
||||
export * from "./credentials-type";
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
import { RadioGroup } from "@nextui-org/react";
|
||||
import { Control, Controller } from "react-hook-form";
|
||||
|
||||
import { CustomRadio } from "@/components/ui/custom";
|
||||
import { FormMessage } from "@/components/ui/form";
|
||||
|
||||
interface RadioGroupM365ViaCredentialsFormProps {
|
||||
control: Control<any>;
|
||||
isInvalid?: boolean;
|
||||
errorMessage?: string;
|
||||
onChange?: (value: string) => void;
|
||||
}
|
||||
|
||||
export const RadioGroupM365ViaCredentialsTypeForm = ({
|
||||
control,
|
||||
isInvalid,
|
||||
errorMessage,
|
||||
onChange,
|
||||
}: RadioGroupM365ViaCredentialsFormProps) => {
|
||||
return (
|
||||
<Controller
|
||||
name="m365CredentialsType"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<>
|
||||
<RadioGroup
|
||||
className="flex flex-wrap"
|
||||
isInvalid={isInvalid}
|
||||
{...field}
|
||||
value={field.value || ""}
|
||||
onValueChange={(value) => {
|
||||
field.onChange(value);
|
||||
if (onChange) {
|
||||
onChange(value);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
<span className="text-sm text-default-500">Application Authentication</span>
|
||||
<CustomRadio
|
||||
description="Connect using Service Principal credentials only"
|
||||
value="credentials"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<span className="ml-2">Service Principal</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
<span className="text-sm text-default-500">
|
||||
Application + User Authentication
|
||||
</span>
|
||||
<CustomRadio
|
||||
description="Connect using Service Principal + User credentials"
|
||||
value="service-principal-user"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<span className="ml-2">Service Principal + User Credentials</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
{errorMessage && (
|
||||
<FormMessage className="text-system-error dark:text-system-error">
|
||||
{errorMessage}
|
||||
</FormMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+38
@@ -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 (
|
||||
<Form {...form}>
|
||||
<RadioGroupM365ViaCredentialsTypeForm
|
||||
control={form.control}
|
||||
isInvalid={!!form.formState.errors.m365CredentialsType}
|
||||
errorMessage={form.formState.errors.m365CredentialsType?.message}
|
||||
onChange={handleSelectionChange}
|
||||
/>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
addCredentialsFormSchema,
|
||||
addCredentialsRoleFormSchema,
|
||||
addCredentialsServiceAccountFormSchema,
|
||||
addCredentialsM365UserFormSchema,
|
||||
ProviderType,
|
||||
} from "@/types";
|
||||
|
||||
@@ -46,6 +47,9 @@ export const useCredentialsForm = ({
|
||||
if (providerType === "gcp" && via === "service-account") {
|
||||
return addCredentialsServiceAccountFormSchema(providerType);
|
||||
}
|
||||
if (providerType === "m365" && via === "service-principal-user") {
|
||||
return addCredentialsM365UserFormSchema();
|
||||
}
|
||||
return addCredentialsFormSchema(providerType);
|
||||
};
|
||||
|
||||
|
||||
@@ -66,6 +66,12 @@ export const awsCredentialsTypeSchema = z.object({
|
||||
}),
|
||||
});
|
||||
|
||||
export const m365CredentialsTypeSchema = z.object({
|
||||
m365CredentialsType: z.string().min(1, {
|
||||
message: "Please select the type of credentials you want to use",
|
||||
}),
|
||||
});
|
||||
|
||||
export const addProviderFormSchema = z
|
||||
.object({
|
||||
providerType: z.enum(["aws", "azure", "gcp", "kubernetes", "m365"], {
|
||||
@@ -254,6 +260,27 @@ export const addCredentialsServiceAccountFormSchema = (
|
||||
[ProviderCredentialFields.PROVIDER_TYPE]: z.string(),
|
||||
});
|
||||
|
||||
export const addCredentialsM365UserFormSchema = () =>
|
||||
z.object({
|
||||
[ProviderCredentialFields.PROVIDER_ID]: z.string(),
|
||||
[ProviderCredentialFields.PROVIDER_TYPE]: z.string(),
|
||||
[ProviderCredentialFields.CLIENT_ID]: z
|
||||
.string()
|
||||
.nonempty("Client ID is required"),
|
||||
[ProviderCredentialFields.CLIENT_SECRET]: z
|
||||
.string()
|
||||
.nonempty("Client Secret is required"),
|
||||
[ProviderCredentialFields.TENANT_ID]: z
|
||||
.string()
|
||||
.nonempty("Tenant ID is required"),
|
||||
[ProviderCredentialFields.USER]: z
|
||||
.string()
|
||||
.nonempty("User is required for this authentication method"),
|
||||
[ProviderCredentialFields.PASSWORD]: z
|
||||
.string()
|
||||
.nonempty("Password is required for this authentication method"),
|
||||
});
|
||||
|
||||
export const testConnectionFormSchema = z.object({
|
||||
[ProviderCredentialFields.PROVIDER_ID]: z.string(),
|
||||
runOnce: z.boolean().default(false),
|
||||
|
||||
Reference in New Issue
Block a user