feat(providers): add GitHub provider support with credential types (#8405)

This commit is contained in:
Pablo Lara
2025-07-30 15:55:40 +02:00
committed by GitHub
parent 7cd1966066
commit 9a622f60fe
26 changed files with 638 additions and 73 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ All notable changes to the **Prowler UI** are documented in this file.
- Lighthouse banner [(#8259)](https://github.com/prowler-cloud/prowler/pull/8259)
- Integration with Amazon S3, enabling storage and retrieval of scan data via S3 buckets [(#8056)](https://github.com/prowler-cloud/prowler/pull/8056)
- Github provider support [(#8405)](https://github.com/prowler-cloud/prowler/pull/8405)
___
## [v1.9.3] (Prowler v5.9.3)
@@ -9,6 +9,8 @@ import {
AddViaServiceAccountForm,
SelectViaGCP,
} from "@/components/providers/workflow/forms/select-credentials-type/gcp";
import { SelectViaGitHub } from "@/components/providers/workflow/forms/select-credentials-type/github";
import { getProviderFormType } from "@/lib/provider-helpers";
import { ProviderType } from "@/types/providers";
interface Props {
@@ -16,30 +18,27 @@ interface Props {
}
export default function AddCredentialsPage({ searchParams }: Props) {
return (
<>
{searchParams.type === "aws" && !searchParams.via && (
<SelectViaAWS initialVia={searchParams.via} />
)}
const { type: providerType, via } = searchParams;
const formType = getProviderFormType(providerType, via);
{searchParams.type === "gcp" && !searchParams.via && (
<SelectViaGCP initialVia={searchParams.via} />
)}
switch (formType) {
case "selector":
if (providerType === "aws") return <SelectViaAWS initialVia={via} />;
if (providerType === "gcp") return <SelectViaGCP initialVia={via} />;
if (providerType === "github")
return <SelectViaGitHub initialVia={via} />;
return null;
{((searchParams.type === "aws" && searchParams.via === "credentials") ||
(searchParams.type === "gcp" && searchParams.via === "credentials") ||
(searchParams.type !== "aws" && searchParams.type !== "gcp")) && (
<AddViaCredentialsForm searchParams={searchParams} />
)}
case "credentials":
return <AddViaCredentialsForm searchParams={searchParams} />;
{searchParams.type === "aws" && searchParams.via === "role" && (
<AddViaRoleForm searchParams={searchParams} />
)}
case "role":
return <AddViaRoleForm searchParams={searchParams} />;
{searchParams.type === "gcp" &&
searchParams.via === "service-account" && (
<AddViaServiceAccountForm searchParams={searchParams} />
)}
</>
);
case "service-account":
return <AddViaServiceAccountForm searchParams={searchParams} />;
default:
return null;
}
}
@@ -6,6 +6,7 @@ import {
UpdateViaRoleForm,
} from "@/components/providers/workflow/forms";
import { UpdateViaServiceAccountForm } from "@/components/providers/workflow/forms/update-via-service-account-key-form";
import { getProviderFormType } from "@/lib/provider-helpers";
import { ProviderType } from "@/types/providers";
interface Props {
@@ -18,30 +19,25 @@ interface Props {
}
export default function UpdateCredentialsPage({ searchParams }: Props) {
return (
<>
{(searchParams.type === "aws" || searchParams.type === "gcp") &&
!searchParams.via && (
<CredentialsUpdateInfo
providerType={searchParams.type}
initialVia={searchParams.via}
/>
)}
const { type: providerType, via } = searchParams;
const formType = getProviderFormType(providerType, via);
{((searchParams.type === "aws" && searchParams.via === "credentials") ||
(searchParams.type === "gcp" && searchParams.via === "credentials") ||
(searchParams.type !== "aws" && searchParams.type !== "gcp")) && (
<UpdateViaCredentialsForm searchParams={searchParams} />
)}
switch (formType) {
case "selector":
return (
<CredentialsUpdateInfo providerType={providerType} initialVia={via} />
);
{searchParams.type === "aws" && searchParams.via === "role" && (
<UpdateViaRoleForm searchParams={searchParams} />
)}
case "credentials":
return <UpdateViaCredentialsForm searchParams={searchParams} />;
{searchParams.type === "gcp" &&
searchParams.via === "service-account" && (
<UpdateViaServiceAccountForm searchParams={searchParams} />
)}
</>
);
case "role":
return <UpdateViaRoleForm searchParams={searchParams} />;
case "service-account":
return <UpdateViaServiceAccountForm searchParams={searchParams} />;
default:
return null;
}
}
@@ -0,0 +1,29 @@
import * as React from "react";
import { IconSvgProps } from "@/types";
export const GitHubProviderBadge: React.FC<IconSvgProps> = ({
size,
width,
height,
...props
}) => (
<svg
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
fill="none"
focusable="false"
height={size || height}
role="presentation"
viewBox="0 0 98 96"
width={size || width}
{...props}
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z"
fill="currentColor"
/>
</svg>
);
@@ -1,5 +1,6 @@
export * from "./aws-provider-badge";
export * from "./azure-provider-badge";
export * from "./gcp-provider-badge";
export * from "./github-provider-badge";
export * from "./ks8-provider-badge";
export * from "./m365-provider-badge";
@@ -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 { SelectViaGitHub } from "@/components/providers/workflow/forms/select-credentials-type/github";
import { ProviderType } from "@/types/providers";
interface UpdateCredentialsInfoProps {
@@ -20,6 +21,9 @@ export const CredentialsUpdateInfo = ({
if (providerType === "gcp") {
return <SelectViaGCP initialVia={initialVia} />;
}
if (providerType === "github") {
return <SelectViaGitHub initialVia={initialVia} />;
}
return null;
};
@@ -11,6 +11,7 @@ import {
AWSProviderBadge,
AzureProviderBadge,
GCPProviderBadge,
GitHubProviderBadge,
KS8ProviderBadge,
M365ProviderBadge,
} from "../icons/providers-badge";
@@ -71,6 +72,12 @@ export const RadioGroupProvider: React.FC<RadioGroupProviderProps> = ({
<span className="ml-2">Kubernetes</span>
</div>
</CustomRadio>
<CustomRadio description="GitHub" value="github">
<div className="flex items-center">
<GitHubProviderBadge size={26} />
<span className="ml-2">GitHub</span>
</div>
</CustomRadio>
</div>
</RadioGroup>
{errorMessage && (
@@ -8,6 +8,7 @@ import { CustomButton } from "@/components/ui/custom";
import { Form } from "@/components/ui/form";
import { useCredentialsForm } from "@/hooks/use-credentials-form";
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
import { requiresBackButton } from "@/lib/provider-helpers";
import {
AWSCredentials,
AWSCredentialsRole,
@@ -25,6 +26,7 @@ import { AWSRoleCredentialsForm } from "./select-credentials-type/aws/credential
import { GCPDefaultCredentialsForm } from "./select-credentials-type/gcp/credentials-type";
import { GCPServiceAccountKeyForm } from "./select-credentials-type/gcp/credentials-type/gcp-service-account-key-form";
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";
@@ -121,26 +123,29 @@ export const BaseCredentialsForm = ({
control={form.control as unknown as Control<KubernetesCredentials>}
/>
)}
{providerType === "github" && (
<GitHubCredentialsForm
control={form.control}
credentialsType={searchParamsObj.get("via") || undefined}
/>
)}
<div className="flex w-full justify-end sm:space-x-6">
{showBackButton &&
(searchParamsObj.get("via") === "credentials" ||
searchParamsObj.get("via") === "role" ||
searchParamsObj.get("via") === "service-account") && (
<CustomButton
type="button"
ariaLabel="Back"
className="w-1/2 bg-transparent"
variant="faded"
size="lg"
radius="lg"
onPress={handleBackStep}
startContent={!isLoading && <ChevronLeftIcon size={24} />}
isDisabled={isLoading}
>
<span>Back</span>
</CustomButton>
)}
{showBackButton && requiresBackButton(searchParamsObj.get("via")) && (
<CustomButton
type="button"
ariaLabel="Back"
className="w-1/2 bg-transparent"
variant="faded"
size="lg"
radius="lg"
onPress={handleBackStep}
startContent={!isLoading && <ChevronLeftIcon size={24} />}
isDisabled={isLoading}
>
<span>Back</span>
</CustomButton>
)}
<CustomButton
type="submit"
ariaLabel="Save"
@@ -7,18 +7,19 @@ import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import * as z from "zod";
import { addProvider } from "@/actions/providers/providers";
import { ProviderTitleDocs } from "@/components/providers/workflow/provider-title-docs";
import { useToast } from "@/components/ui";
import { CustomButton, CustomInput } from "@/components/ui/custom";
import { Form } from "@/components/ui/form";
import { addProviderFormSchema, ApiError, ProviderType } from "@/types";
import { addProvider } from "../../../../actions/providers/providers";
import { addProviderFormSchema, ApiError } from "../../../../types";
import { RadioGroupProvider } from "../../radio-group-provider";
import { ProviderTitleDocs } from "../provider-title-docs";
export type FormValues = z.infer<typeof addProviderFormSchema>;
// 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", "");
@@ -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<any> }) => {
return (
<>
<div className="flex flex-col">
<div className="text-md font-bold leading-9 text-default-foreground">
Connect via GitHub App
</div>
<div className="text-sm text-default-500">
Please provide your GitHub App ID and private key.
</div>
</div>
<CustomInput
control={control}
name={ProviderCredentialFields.GITHUB_APP_ID}
type="text"
label="GitHub App ID"
labelPlacement="inside"
placeholder="Enter your GitHub App ID"
variant="bordered"
isRequired
isInvalid={
!!control._formState.errors[ProviderCredentialFields.GITHUB_APP_ID]
}
/>
<CustomTextarea
control={control}
name={ProviderCredentialFields.GITHUB_APP_KEY}
label="GitHub App Private Key"
labelPlacement="inside"
placeholder="Paste your GitHub App private key here"
variant="bordered"
isRequired
minRows={4}
isInvalid={
!!control._formState.errors[ProviderCredentialFields.GITHUB_APP_KEY]
}
/>
</>
);
};
@@ -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<any> }) => {
return (
<>
<div className="flex flex-col">
<div className="text-md font-bold leading-9 text-default-foreground">
Connect via OAuth App
</div>
<div className="text-sm text-default-500">
Please provide your GitHub OAuth App token.
</div>
</div>
<CustomInput
control={control}
name={ProviderCredentialFields.OAUTH_APP_TOKEN}
type="password"
label="OAuth App Token"
labelPlacement="inside"
placeholder="Enter your GitHub OAuth App token"
variant="bordered"
isRequired
isInvalid={
!!control._formState.errors[ProviderCredentialFields.OAUTH_APP_TOKEN]
}
/>
</>
);
};
@@ -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<any>;
}) => {
return (
<>
<div className="flex flex-col">
<div className="text-md font-bold leading-9 text-default-foreground">
Connect via Personal Access Token
</div>
<div className="text-sm text-default-500">
Please provide your GitHub personal access token.
</div>
</div>
<CustomInput
control={control}
name={ProviderCredentialFields.PERSONAL_ACCESS_TOKEN}
type="password"
label="Personal Access Token"
labelPlacement="inside"
placeholder="Enter your GitHub personal access token"
variant="bordered"
isRequired
isInvalid={
!!control._formState.errors[
ProviderCredentialFields.PERSONAL_ACCESS_TOKEN
]
}
/>
</>
);
};
@@ -0,0 +1,3 @@
export * from "./github-app-form";
export * from "./github-oauth-app-form";
export * from "./github-personal-access-token-form";
@@ -0,0 +1,3 @@
export * from "./credentials-type";
export * from "./radio-group-github-via-credentials-type-form";
export * from "./select-via-github";
@@ -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<any>;
isInvalid: boolean;
errorMessage?: string;
onChange?: (value: string) => void;
};
export const RadioGroupGitHubViaCredentialsTypeForm = ({
control,
isInvalid,
errorMessage,
onChange,
}: RadioGroupGitHubViaCredentialsFormProps) => {
return (
<Controller
name="githubCredentialsType"
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">
Personal Access Token
</span>
<CustomRadio
description="Use a personal access token for authentication"
value="personal_access_token"
>
<div className="flex items-center">
<span className="ml-2">Personal Access Token</span>
</div>
</CustomRadio>
<span className="text-sm text-default-500">OAuth App</span>
<CustomRadio
description="Use OAuth App token for authentication"
value="oauth_app"
>
<div className="flex items-center">
<span className="ml-2">OAuth App Token</span>
</div>
</CustomRadio>
<span className="text-sm text-default-500">GitHub App</span>
<CustomRadio
description="Use GitHub App ID and private key for authentication"
value="github_app"
>
<div className="flex items-center">
<span className="ml-2">GitHub App</span>
</div>
</CustomRadio>
</div>
</RadioGroup>
{errorMessage && (
<FormMessage className="text-system-error dark:text-system-error">
{errorMessage}
</FormMessage>
)}
</>
)}
/>
);
};
@@ -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 (
<Form {...form}>
<RadioGroupGitHubViaCredentialsTypeForm
control={form.control}
isInvalid={!!form.formState.errors.githubCredentialsType}
errorMessage={form.formState.errors.githubCredentialsType?.message}
onChange={handleSelectionChange}
/>
</Form>
);
};
@@ -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<any>;
credentialsType?: string;
}
export const GitHubCredentialsForm = ({
control,
credentialsType,
}: GitHubCredentialsFormProps) => {
switch (credentialsType) {
case "personal_access_token":
return <GitHubPersonalAccessTokenForm control={control} />;
case "oauth_app":
return <GitHubOAuthAppForm control={control} />;
case "github_app":
return <GitHubAppForm control={control} />;
default:
return null;
}
};
@@ -1,3 +1,4 @@
export * from "./azure-credentials-form";
export * from "./github-credentials-form";
export * from "./k8s-credentials-form";
export * from "./m365-credentials-form";
@@ -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 <KS8ProviderBadge width={35} height={35} />;
case "m365":
return <M365ProviderBadge width={35} height={35} />;
case "github":
return <GitHubProviderBadge width={35} height={35} />;
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";
}
+26
View File
@@ -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;
}
+5
View File
@@ -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?",
@@ -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];
@@ -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];
+65
View File
@@ -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);
};
+63 -4
View File
@@ -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<string, any>, 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) =>
+10 -1
View File
@@ -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;