- {showBackButton &&
- (searchParamsObj.get("via") === "credentials" ||
- searchParamsObj.get("via") === "role" ||
- searchParamsObj.get("via") === "service-account") && (
-
}
- isDisabled={isLoading}
- >
-
Back
-
- )}
+ {showBackButton && requiresBackButton(searchParamsObj.get("via")) && (
+
}
+ isDisabled={isLoading}
+ >
+
Back
+
+ )}
;
// Helper function for labels and placeholders
-const getProviderFieldDetails = (providerType?: string) => {
+const getProviderFieldDetails = (providerType?: ProviderType) => {
switch (providerType) {
case "aws":
return {
@@ -45,6 +46,11 @@ const getProviderFieldDetails = (providerType?: string) => {
label: "Domain ID",
placeholder: "e.g. your-domain.onmicrosoft.com",
};
+ case "github":
+ return {
+ label: "Username",
+ placeholder: "e.g. your-github-username",
+ };
default:
return {
label: "Provider UID",
@@ -142,6 +148,10 @@ export const ConnectAccountForm = () => {
const handleBackStep = () => {
setPrevStep((prev) => prev - 1);
+ //Deselect the providerType if the user is going back to the first step
+ if (prevStep === 2) {
+ form.setValue("providerType", undefined as unknown as ProviderType);
+ }
// Reset the providerUid and providerAlias fields when going back
form.setValue("providerUid", "");
form.setValue("providerAlias", "");
diff --git a/ui/components/providers/workflow/forms/select-credentials-type/github/credentials-type/github-app-form.tsx b/ui/components/providers/workflow/forms/select-credentials-type/github/credentials-type/github-app-form.tsx
new file mode 100644
index 0000000000..1d9f77f1bf
--- /dev/null
+++ b/ui/components/providers/workflow/forms/select-credentials-type/github/credentials-type/github-app-form.tsx
@@ -0,0 +1,47 @@
+"use client";
+
+import { Control } from "react-hook-form";
+
+import { CustomInput, CustomTextarea } from "@/components/ui/custom";
+import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
+
+export const GitHubAppForm = ({ control }: { control: Control }) => {
+ return (
+ <>
+
+
+ Connect via GitHub App
+
+
+ Please provide your GitHub App ID and private key.
+
+
+
+
+ >
+ );
+};
diff --git a/ui/components/providers/workflow/forms/select-credentials-type/github/credentials-type/github-oauth-app-form.tsx b/ui/components/providers/workflow/forms/select-credentials-type/github/credentials-type/github-oauth-app-form.tsx
new file mode 100644
index 0000000000..115f46bebf
--- /dev/null
+++ b/ui/components/providers/workflow/forms/select-credentials-type/github/credentials-type/github-oauth-app-form.tsx
@@ -0,0 +1,34 @@
+"use client";
+
+import { Control } from "react-hook-form";
+
+import { CustomInput } from "@/components/ui/custom";
+import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
+
+export const GitHubOAuthAppForm = ({ control }: { control: Control }) => {
+ return (
+ <>
+
+
+ Connect via OAuth App
+
+
+ Please provide your GitHub OAuth App token.
+
+
+
+ >
+ );
+};
diff --git a/ui/components/providers/workflow/forms/select-credentials-type/github/credentials-type/github-personal-access-token-form.tsx b/ui/components/providers/workflow/forms/select-credentials-type/github/credentials-type/github-personal-access-token-form.tsx
new file mode 100644
index 0000000000..460ae47d81
--- /dev/null
+++ b/ui/components/providers/workflow/forms/select-credentials-type/github/credentials-type/github-personal-access-token-form.tsx
@@ -0,0 +1,40 @@
+"use client";
+
+import { Control } from "react-hook-form";
+
+import { CustomInput } from "@/components/ui/custom";
+import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
+
+export const GitHubPersonalAccessTokenForm = ({
+ control,
+}: {
+ control: Control;
+}) => {
+ return (
+ <>
+
+
+ Connect via Personal Access Token
+
+
+ Please provide your GitHub personal access token.
+
+
+
+ >
+ );
+};
diff --git a/ui/components/providers/workflow/forms/select-credentials-type/github/credentials-type/index.ts b/ui/components/providers/workflow/forms/select-credentials-type/github/credentials-type/index.ts
new file mode 100644
index 0000000000..e359315250
--- /dev/null
+++ b/ui/components/providers/workflow/forms/select-credentials-type/github/credentials-type/index.ts
@@ -0,0 +1,3 @@
+export * from "./github-app-form";
+export * from "./github-oauth-app-form";
+export * from "./github-personal-access-token-form";
diff --git a/ui/components/providers/workflow/forms/select-credentials-type/github/index.ts b/ui/components/providers/workflow/forms/select-credentials-type/github/index.ts
new file mode 100644
index 0000000000..3377bb1fd6
--- /dev/null
+++ b/ui/components/providers/workflow/forms/select-credentials-type/github/index.ts
@@ -0,0 +1,3 @@
+export * from "./credentials-type";
+export * from "./radio-group-github-via-credentials-type-form";
+export * from "./select-via-github";
diff --git a/ui/components/providers/workflow/forms/select-credentials-type/github/radio-group-github-via-credentials-type-form.tsx b/ui/components/providers/workflow/forms/select-credentials-type/github/radio-group-github-via-credentials-type-form.tsx
new file mode 100644
index 0000000000..0b24da9bf0
--- /dev/null
+++ b/ui/components/providers/workflow/forms/select-credentials-type/github/radio-group-github-via-credentials-type-form.tsx
@@ -0,0 +1,84 @@
+"use client";
+
+import { RadioGroup } from "@nextui-org/react";
+import React from "react";
+import { Control, Controller } from "react-hook-form";
+
+import { CustomRadio } from "@/components/ui/custom";
+import { FormMessage } from "@/components/ui/form";
+
+type RadioGroupGitHubViaCredentialsFormProps = {
+ control: Control;
+ isInvalid: boolean;
+ errorMessage?: string;
+ onChange?: (value: string) => void;
+};
+
+export const RadioGroupGitHubViaCredentialsTypeForm = ({
+ control,
+ isInvalid,
+ errorMessage,
+ onChange,
+}: RadioGroupGitHubViaCredentialsFormProps) => {
+ return (
+ (
+ <>
+ {
+ field.onChange(value);
+ if (onChange) {
+ onChange(value);
+ }
+ }}
+ >
+
+
+ Personal Access Token
+
+
+
+ Personal Access Token
+
+
+
+
OAuth App
+
+
+ OAuth App Token
+
+
+
+
GitHub App
+
+
+ GitHub App
+
+
+
+
+ {errorMessage && (
+
+ {errorMessage}
+
+ )}
+ >
+ )}
+ />
+ );
+};
diff --git a/ui/components/providers/workflow/forms/select-credentials-type/github/select-via-github.tsx b/ui/components/providers/workflow/forms/select-credentials-type/github/select-via-github.tsx
new file mode 100644
index 0000000000..9fe491f4ca
--- /dev/null
+++ b/ui/components/providers/workflow/forms/select-credentials-type/github/select-via-github.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 { RadioGroupGitHubViaCredentialsTypeForm } from "./radio-group-github-via-credentials-type-form";
+
+interface SelectViaGitHubProps {
+ initialVia?: string;
+}
+
+export const SelectViaGitHub = ({ initialVia }: SelectViaGitHubProps) => {
+ const router = useRouter();
+ const form = useForm({
+ defaultValues: {
+ githubCredentialsType: 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/github-credentials-form.tsx b/ui/components/providers/workflow/forms/via-credentials/github-credentials-form.tsx
new file mode 100644
index 0000000000..94d3443cb7
--- /dev/null
+++ b/ui/components/providers/workflow/forms/via-credentials/github-credentials-form.tsx
@@ -0,0 +1,30 @@
+"use client";
+
+import { Control } from "react-hook-form";
+
+import {
+ GitHubAppForm,
+ GitHubOAuthAppForm,
+ GitHubPersonalAccessTokenForm,
+} from "../select-credentials-type/github";
+
+interface GitHubCredentialsFormProps {
+ control: Control;
+ credentialsType?: string;
+}
+
+export const GitHubCredentialsForm = ({
+ control,
+ credentialsType,
+}: GitHubCredentialsFormProps) => {
+ switch (credentialsType) {
+ case "personal_access_token":
+ return ;
+ case "oauth_app":
+ return ;
+ case "github_app":
+ return ;
+ default:
+ return null;
+ }
+};
diff --git a/ui/components/providers/workflow/forms/via-credentials/index.ts b/ui/components/providers/workflow/forms/via-credentials/index.ts
index 9058fc7362..d020b9715f 100644
--- a/ui/components/providers/workflow/forms/via-credentials/index.ts
+++ b/ui/components/providers/workflow/forms/via-credentials/index.ts
@@ -1,3 +1,4 @@
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/ui/entities/get-provider-logo.tsx b/ui/components/ui/entities/get-provider-logo.tsx
index 17ab559988..047652ffdc 100644
--- a/ui/components/ui/entities/get-provider-logo.tsx
+++ b/ui/components/ui/entities/get-provider-logo.tsx
@@ -4,6 +4,7 @@ import {
AWSProviderBadge,
AzureProviderBadge,
GCPProviderBadge,
+ GitHubProviderBadge,
KS8ProviderBadge,
M365ProviderBadge,
} from "@/components/icons/providers-badge";
@@ -21,6 +22,8 @@ export const getProviderLogo = (provider: ProviderType) => {
return ;
case "m365":
return ;
+ case "github":
+ return ;
default:
return null;
}
@@ -38,6 +41,8 @@ export const getProviderName = (provider: ProviderType): string => {
return "Kubernetes";
case "m365":
return "Microsoft 365";
+ case "github":
+ return "GitHub";
default:
return "Unknown Provider";
}
diff --git a/ui/hooks/use-credentials-form.ts b/ui/hooks/use-credentials-form.ts
index df6ffa9517..317d1749ae 100644
--- a/ui/hooks/use-credentials-form.ts
+++ b/ui/hooks/use-credentials-form.ts
@@ -46,6 +46,10 @@ 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") {
+ return addCredentialsFormSchema(providerType, via);
+ }
return addCredentialsFormSchema(providerType);
};
@@ -117,6 +121,28 @@ export const useCredentialsForm = ({
...baseDefaults,
[ProviderCredentialFields.KUBECONFIG_CONTENT]: "",
};
+ case "github":
+ // GitHub credentials based on via parameter
+ if (via === "personal_access_token") {
+ return {
+ ...baseDefaults,
+ [ProviderCredentialFields.PERSONAL_ACCESS_TOKEN]: "",
+ };
+ }
+ if (via === "oauth_app") {
+ return {
+ ...baseDefaults,
+ [ProviderCredentialFields.OAUTH_APP_TOKEN]: "",
+ };
+ }
+ if (via === "github_app") {
+ return {
+ ...baseDefaults,
+ [ProviderCredentialFields.GITHUB_APP_ID]: "",
+ [ProviderCredentialFields.GITHUB_APP_KEY]: "",
+ };
+ }
+ return baseDefaults;
default:
return baseDefaults;
}
diff --git a/ui/lib/external-urls.ts b/ui/lib/external-urls.ts
index cea7a694ae..1e8210a116 100644
--- a/ui/lib/external-urls.ts
+++ b/ui/lib/external-urls.ts
@@ -25,6 +25,11 @@ export const getProviderHelpText = (provider: string) => {
text: "Need help connecting your Kubernetes cluster?",
link: "https://goto.prowler.com/provider-k8s",
};
+ case "github":
+ return {
+ text: "Need help connecting your GitHub account?",
+ link: "https://goto.prowler.com/provider-github",
+ };
default:
return {
text: "How to setup a provider?",
diff --git a/ui/lib/provider-credentials/build-crendentials.ts b/ui/lib/provider-credentials/build-crendentials.ts
index 791a209c8c..0e2c96f990 100644
--- a/ui/lib/provider-credentials/build-crendentials.ts
+++ b/ui/lib/provider-credentials/build-crendentials.ts
@@ -147,6 +147,56 @@ export const buildKubernetesSecret = (formData: FormData) => {
return filterEmptyValues(secret);
};
+export const buildGitHubSecret = (formData: FormData) => {
+ // Check which authentication method is being used
+ const hasPersonalToken =
+ formData.get(ProviderCredentialFields.PERSONAL_ACCESS_TOKEN) !== null &&
+ formData.get(ProviderCredentialFields.PERSONAL_ACCESS_TOKEN) !== "";
+ const hasOAuthToken =
+ formData.get(ProviderCredentialFields.OAUTH_APP_TOKEN) !== null &&
+ formData.get(ProviderCredentialFields.OAUTH_APP_TOKEN) !== "";
+ const hasGitHubApp =
+ formData.get(ProviderCredentialFields.GITHUB_APP_ID) !== null &&
+ formData.get(ProviderCredentialFields.GITHUB_APP_ID) !== "";
+
+ if (hasPersonalToken) {
+ const secret = {
+ [ProviderCredentialFields.PERSONAL_ACCESS_TOKEN]: getFormValue(
+ formData,
+ ProviderCredentialFields.PERSONAL_ACCESS_TOKEN,
+ ),
+ };
+ return filterEmptyValues(secret);
+ }
+
+ if (hasOAuthToken) {
+ const secret = {
+ [ProviderCredentialFields.OAUTH_APP_TOKEN]: getFormValue(
+ formData,
+ ProviderCredentialFields.OAUTH_APP_TOKEN,
+ ),
+ };
+ return filterEmptyValues(secret);
+ }
+
+ if (hasGitHubApp) {
+ const secret = {
+ [ProviderCredentialFields.GITHUB_APP_ID]: getFormValue(
+ formData,
+ ProviderCredentialFields.GITHUB_APP_ID,
+ ),
+ [ProviderCredentialFields.GITHUB_APP_KEY]: getFormValue(
+ formData,
+ ProviderCredentialFields.GITHUB_APP_KEY,
+ ),
+ };
+ return filterEmptyValues(secret);
+ }
+
+ // If no credentials are provided, return empty object
+ return {};
+};
+
// Main function to build secret configuration
export const buildSecretConfig = (
formData: FormData,
@@ -177,6 +227,10 @@ export const buildSecretConfig = (
secretType: "static",
secret: buildKubernetesSecret(formData),
}),
+ github: () => ({
+ secretType: "static",
+ secret: buildGitHubSecret(formData),
+ }),
};
const builder = secretBuilders[providerType];
diff --git a/ui/lib/provider-credentials/provider-credential-fields.ts b/ui/lib/provider-credentials/provider-credential-fields.ts
index 5cbd6605b9..32dc184292 100644
--- a/ui/lib/provider-credentials/provider-credential-fields.ts
+++ b/ui/lib/provider-credentials/provider-credential-fields.ts
@@ -8,6 +8,7 @@ export const ProviderCredentialFields = {
CREDENTIALS_TYPE: "credentials_type",
CREDENTIALS_TYPE_AWS: "aws-sdk-default",
CREDENTIALS_TYPE_ACCESS_SECRET_KEY: "access-secret-key",
+
// Base fields for all providers
PROVIDER_ID: "providerId",
PROVIDER_TYPE: "providerType",
@@ -35,6 +36,12 @@ export const ProviderCredentialFields = {
// Kubernetes fields
KUBECONFIG_CONTENT: "kubeconfig_content",
+
+ // GitHub fields
+ PERSONAL_ACCESS_TOKEN: "personal_access_token",
+ OAUTH_APP_TOKEN: "oauth_app_token",
+ GITHUB_APP_ID: "github_app_id",
+ GITHUB_APP_KEY: "github_app_key_content",
} as const;
// Type for credential field values
@@ -59,6 +66,10 @@ export const ErrorPointers = {
SESSION_DURATION: "/data/attributes/secret/session_duration",
ROLE_SESSION_NAME: "/data/attributes/secret/role_session_name",
SERVICE_ACCOUNT_KEY: "/data/attributes/secret/service_account_key",
+ PERSONAL_ACCESS_TOKEN: "/data/attributes/secret/personal_access_token",
+ 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",
} 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 d1247c3eb4..becd9bc4f2 100644
--- a/ui/lib/provider-helpers.ts
+++ b/ui/lib/provider-helpers.ts
@@ -2,6 +2,7 @@ import {
ProviderEntity,
ProviderProps,
ProvidersApiResponse,
+ ProviderType,
} from "@/types/providers";
export const extractProviderUIDs = (
@@ -38,3 +39,67 @@ export const createProviderDetailsMapping = (
};
});
};
+
+// Helper function to determine which form type to show
+export type ProviderFormType =
+ | "selector"
+ | "credentials"
+ | "role"
+ | "service-account"
+ | null;
+
+export const getProviderFormType = (
+ providerType: ProviderType,
+ via?: string,
+): ProviderFormType => {
+ // Providers that need credential type selection
+ const needsSelector = ["aws", "gcp", "github"].includes(providerType);
+
+ // Show selector if no via parameter and provider needs it
+ if (needsSelector && !via) {
+ return "selector";
+ }
+
+ // AWS specific forms
+ if (providerType === "aws") {
+ if (via === "role") return "role";
+ if (via === "credentials") return "credentials";
+ }
+
+ // GCP specific forms
+ if (providerType === "gcp") {
+ if (via === "service-account") return "service-account";
+ if (via === "credentials") return "credentials";
+ }
+
+ // GitHub credential types
+ if (
+ providerType === "github" &&
+ ["personal_access_token", "oauth_app", "github_app"].includes(via || "")
+ ) {
+ return "credentials";
+ }
+
+ // Other providers go directly to credentials form
+ if (!needsSelector) {
+ return "credentials";
+ }
+
+ return null;
+};
+
+// Helper to check if back button should be shown based on via parameter
+export const requiresBackButton = (via?: string | null): boolean => {
+ if (!via) return false;
+
+ const validViaTypes = [
+ "credentials",
+ "role",
+ "service-account",
+ "personal_access_token",
+ "oauth_app",
+ "github_app",
+ ];
+
+ return validViaTypes.includes(via);
+};
diff --git a/ui/types/formSchemas.ts b/ui/types/formSchemas.ts
index 2211a7a2cb..b1deadf489 100644
--- a/ui/types/formSchemas.ts
+++ b/ui/types/formSchemas.ts
@@ -3,7 +3,7 @@ import { z } from "zod";
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
import { validateMutelistYaml, validateYaml } from "@/lib/yaml";
-import { ProviderType } from "./providers";
+import { PROVIDER_TYPES, ProviderType } from "./providers";
export const addRoleFormSchema = z.object({
name: z.string().min(1, "Name is required"),
@@ -71,7 +71,7 @@ export const awsCredentialsTypeSchema = z.object({
export const addProviderFormSchema = z
.object({
- providerType: z.enum(["aws", "azure", "gcp", "kubernetes", "m365"], {
+ providerType: z.enum(PROVIDER_TYPES, {
required_error: "Please select a provider type",
}),
})
@@ -105,10 +105,18 @@ export const addProviderFormSchema = z
providerUid: z.string(),
awsCredentialsType: z.string().optional(),
}),
+ z.object({
+ providerType: z.literal("github"),
+ [ProviderCredentialFields.PROVIDER_ALIAS]: z.string(),
+ providerUid: z.string(),
+ }),
]),
);
-export const addCredentialsFormSchema = (providerType: string) =>
+export const addCredentialsFormSchema = (
+ providerType: ProviderType,
+ via?: string | null,
+) =>
z
.object({
[ProviderCredentialFields.PROVIDER_ID]: z.string(),
@@ -167,7 +175,22 @@ export const addCredentialsFormSchema = (providerType: string) =>
[ProviderCredentialFields.USER]: z.string().optional(),
[ProviderCredentialFields.PASSWORD]: z.string().optional(),
}
- : {}),
+ : providerType === "github"
+ ? {
+ [ProviderCredentialFields.PERSONAL_ACCESS_TOKEN]: z
+ .string()
+ .optional(),
+ [ProviderCredentialFields.OAUTH_APP_TOKEN]: z
+ .string()
+ .optional(),
+ [ProviderCredentialFields.GITHUB_APP_ID]: z
+ .string()
+ .optional(),
+ [ProviderCredentialFields.GITHUB_APP_KEY]: z
+ .string()
+ .optional(),
+ }
+ : {}),
})
.superRefine((data: Record, ctx) => {
if (providerType === "m365") {
@@ -190,6 +213,42 @@ export const addCredentialsFormSchema = (providerType: string) =>
});
}
}
+
+ if (providerType === "github") {
+ // For GitHub, validation depends on the 'via' parameter
+ if (via === "personal_access_token") {
+ if (!data[ProviderCredentialFields.PERSONAL_ACCESS_TOKEN]) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: "Personal Access Token is required",
+ path: [ProviderCredentialFields.PERSONAL_ACCESS_TOKEN],
+ });
+ }
+ } else if (via === "oauth_app") {
+ if (!data[ProviderCredentialFields.OAUTH_APP_TOKEN]) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: "OAuth App Token is required",
+ path: [ProviderCredentialFields.OAUTH_APP_TOKEN],
+ });
+ }
+ } else if (via === "github_app") {
+ if (!data[ProviderCredentialFields.GITHUB_APP_ID]) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: "GitHub App ID is required",
+ path: [ProviderCredentialFields.GITHUB_APP_ID],
+ });
+ }
+ if (!data[ProviderCredentialFields.GITHUB_APP_KEY]) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: "GitHub App Private Key is required",
+ path: [ProviderCredentialFields.GITHUB_APP_KEY],
+ });
+ }
+ }
+ }
});
export const addCredentialsRoleFormSchema = (providerType: string) =>
diff --git a/ui/types/providers.ts b/ui/types/providers.ts
index b7aae5b944..0967d3304e 100644
--- a/ui/types/providers.ts
+++ b/ui/types/providers.ts
@@ -1,4 +1,13 @@
-export type ProviderType = "aws" | "azure" | "m365" | "gcp" | "kubernetes";
+export const PROVIDER_TYPES = [
+ "aws",
+ "azure",
+ "gcp",
+ "kubernetes",
+ "m365",
+ "github",
+] as const;
+
+export type ProviderType = (typeof PROVIDER_TYPES)[number];
export interface ProviderProps {
id: string;