mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Merge pull request #4743 from Infisical/feat/adds-PAT-to-github-integration
feat: adds PAT to GitHub integration
This commit is contained in:
@@ -345,6 +345,8 @@ export const getAppConnectionMethodName = (method: TAppConnection["method"]) =>
|
|||||||
case GitHubConnectionMethod.App:
|
case GitHubConnectionMethod.App:
|
||||||
case GitHubRadarConnectionMethod.App:
|
case GitHubRadarConnectionMethod.App:
|
||||||
return "GitHub App";
|
return "GitHub App";
|
||||||
|
case GitHubConnectionMethod.Pat:
|
||||||
|
return "Personal Access Token";
|
||||||
case AzureKeyVaultConnectionMethod.OAuth:
|
case AzureKeyVaultConnectionMethod.OAuth:
|
||||||
case AzureAppConfigurationConnectionMethod.OAuth:
|
case AzureAppConfigurationConnectionMethod.OAuth:
|
||||||
case AzureClientSecretsConnectionMethod.OAuth:
|
case AzureClientSecretsConnectionMethod.OAuth:
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export enum GitHubConnectionMethod {
|
export enum GitHubConnectionMethod {
|
||||||
OAuth = "oauth",
|
OAuth = "oauth",
|
||||||
App = "github-app"
|
App = "github-app",
|
||||||
|
Pat = "pat"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -248,10 +248,18 @@ export const makePaginatedGitHubRequest = async <T, R = T[]>(
|
|||||||
): Promise<T[]> => {
|
): Promise<T[]> => {
|
||||||
const { credentials, method } = appConnection;
|
const { credentials, method } = appConnection;
|
||||||
|
|
||||||
const token =
|
let token: string;
|
||||||
method === GitHubConnectionMethod.OAuth
|
|
||||||
? credentials.accessToken
|
switch (method) {
|
||||||
: await getGitHubAppAuthToken(appConnection, gatewayService, gatewayV2Service);
|
case GitHubConnectionMethod.OAuth:
|
||||||
|
token = credentials.accessToken;
|
||||||
|
break;
|
||||||
|
case GitHubConnectionMethod.Pat:
|
||||||
|
token = credentials.personalAccessToken;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
token = await getGitHubAppAuthToken(appConnection, gatewayService, gatewayV2Service);
|
||||||
|
}
|
||||||
|
|
||||||
const baseUrl = `https://${await getGitHubInstanceApiUrl(appConnection)}${path}`;
|
const baseUrl = `https://${await getGitHubInstanceApiUrl(appConnection)}${path}`;
|
||||||
const initialUrlObj = new URL(baseUrl);
|
const initialUrlObj = new URL(baseUrl);
|
||||||
@@ -460,6 +468,35 @@ export const validateGitHubConnectionCredentials = async (
|
|||||||
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">
|
gatewayV2Service: Pick<TGatewayV2ServiceFactory, "getPlatformConnectionDetailsByGatewayId">
|
||||||
) => {
|
) => {
|
||||||
const { credentials, method } = config;
|
const { credentials, method } = config;
|
||||||
|
|
||||||
|
// PAT validation
|
||||||
|
if (method === GitHubConnectionMethod.Pat) {
|
||||||
|
try {
|
||||||
|
const apiUrl = await getGitHubInstanceApiUrl(config);
|
||||||
|
await requestWithGitHubGateway(config, gatewayService, gatewayV2Service, {
|
||||||
|
url: `https://${apiUrl}/user`,
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
Accept: "application/vnd.github+json",
|
||||||
|
Authorization: `Bearer ${credentials.personalAccessToken}`,
|
||||||
|
"X-GitHub-Api-Version": "2022-11-28"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
personalAccessToken: credentials.personalAccessToken,
|
||||||
|
instanceType: credentials.instanceType,
|
||||||
|
host: credentials.host
|
||||||
|
};
|
||||||
|
} catch (e: unknown) {
|
||||||
|
logger.error(e, "Unable to verify GitHub PAT connection");
|
||||||
|
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Unable to validate Personal Access Token: verify token has proper permissions"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_ID,
|
INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_ID,
|
||||||
INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_SECRET,
|
INF_APP_CONNECTION_GITHUB_OAUTH_CLIENT_SECRET,
|
||||||
|
|||||||
@@ -38,6 +38,19 @@ export const GitHubConnectionAppInputCredentialsSchema = z.union([
|
|||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
export const GitHubConnectionPatInputCredentialsSchema = z.union([
|
||||||
|
z.object({
|
||||||
|
personalAccessToken: z.string().trim().min(1, "Personal Access Token required"),
|
||||||
|
instanceType: z.literal("server"),
|
||||||
|
host: z.string().trim().min(1, "Host is required for server instance type")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
personalAccessToken: z.string().trim().min(1, "Personal Access Token required"),
|
||||||
|
instanceType: z.literal("cloud").optional(),
|
||||||
|
host: z.string().trim().optional()
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
export const GitHubConnectionOAuthOutputCredentialsSchema = z.union([
|
export const GitHubConnectionOAuthOutputCredentialsSchema = z.union([
|
||||||
z.object({
|
z.object({
|
||||||
accessToken: z.string(),
|
accessToken: z.string(),
|
||||||
@@ -64,6 +77,19 @@ export const GitHubConnectionAppOutputCredentialsSchema = z.union([
|
|||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
export const GitHubConnectionPatOutputCredentialsSchema = z.union([
|
||||||
|
z.object({
|
||||||
|
personalAccessToken: z.string(),
|
||||||
|
instanceType: z.literal("server"),
|
||||||
|
host: z.string().trim().min(1)
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
personalAccessToken: z.string(),
|
||||||
|
instanceType: z.literal("cloud").optional(),
|
||||||
|
host: z.string().trim().optional()
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
export const ValidateGitHubConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
export const ValidateGitHubConnectionCredentialsSchema = z.discriminatedUnion("method", [
|
||||||
z.object({
|
z.object({
|
||||||
method: z.literal(GitHubConnectionMethod.App).describe(AppConnections.CREATE(AppConnection.GitHub).method),
|
method: z.literal(GitHubConnectionMethod.App).describe(AppConnections.CREATE(AppConnection.GitHub).method),
|
||||||
@@ -76,6 +102,12 @@ export const ValidateGitHubConnectionCredentialsSchema = z.discriminatedUnion("m
|
|||||||
credentials: GitHubConnectionOAuthInputCredentialsSchema.describe(
|
credentials: GitHubConnectionOAuthInputCredentialsSchema.describe(
|
||||||
AppConnections.CREATE(AppConnection.GitHub).credentials
|
AppConnections.CREATE(AppConnection.GitHub).credentials
|
||||||
)
|
)
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
method: z.literal(GitHubConnectionMethod.Pat).describe(AppConnections.CREATE(AppConnection.GitHub).method),
|
||||||
|
credentials: GitHubConnectionPatInputCredentialsSchema.describe(
|
||||||
|
AppConnections.CREATE(AppConnection.GitHub).credentials
|
||||||
|
)
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -88,7 +120,11 @@ export const CreateGitHubConnectionSchema = ValidateGitHubConnectionCredentialsS
|
|||||||
export const UpdateGitHubConnectionSchema = z
|
export const UpdateGitHubConnectionSchema = z
|
||||||
.object({
|
.object({
|
||||||
credentials: z
|
credentials: z
|
||||||
.union([GitHubConnectionAppInputCredentialsSchema, GitHubConnectionOAuthInputCredentialsSchema])
|
.union([
|
||||||
|
GitHubConnectionAppInputCredentialsSchema,
|
||||||
|
GitHubConnectionOAuthInputCredentialsSchema,
|
||||||
|
GitHubConnectionPatInputCredentialsSchema
|
||||||
|
])
|
||||||
.optional()
|
.optional()
|
||||||
.describe(AppConnections.UPDATE(AppConnection.GitHub).credentials)
|
.describe(AppConnections.UPDATE(AppConnection.GitHub).credentials)
|
||||||
})
|
})
|
||||||
@@ -110,6 +146,10 @@ export const GitHubConnectionSchema = z.intersection(
|
|||||||
z.object({
|
z.object({
|
||||||
method: z.literal(GitHubConnectionMethod.OAuth),
|
method: z.literal(GitHubConnectionMethod.OAuth),
|
||||||
credentials: GitHubConnectionOAuthOutputCredentialsSchema
|
credentials: GitHubConnectionOAuthOutputCredentialsSchema
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
method: z.literal(GitHubConnectionMethod.Pat),
|
||||||
|
credentials: GitHubConnectionPatOutputCredentialsSchema
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
@@ -128,6 +168,13 @@ export const SanitizedGitHubConnectionSchema = z.discriminatedUnion("method", [
|
|||||||
instanceType: z.union([z.literal("server"), z.literal("cloud")]).optional(),
|
instanceType: z.union([z.literal("server"), z.literal("cloud")]).optional(),
|
||||||
host: z.string().optional()
|
host: z.string().optional()
|
||||||
})
|
})
|
||||||
|
}),
|
||||||
|
BaseGitHubConnectionSchema.extend({
|
||||||
|
method: z.literal(GitHubConnectionMethod.Pat),
|
||||||
|
credentials: z.object({
|
||||||
|
instanceType: z.union([z.literal("server"), z.literal("cloud")]).optional(),
|
||||||
|
host: z.string().optional()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -211,10 +211,18 @@ export const GithubSyncFns = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { connection } = secretSync;
|
const { connection } = secretSync;
|
||||||
const token =
|
let token: string;
|
||||||
connection.method === GitHubConnectionMethod.OAuth
|
|
||||||
? connection.credentials.accessToken
|
switch (connection.method) {
|
||||||
: await getGitHubAppAuthToken(connection, gatewayService, gatewayV2Service);
|
case GitHubConnectionMethod.OAuth:
|
||||||
|
token = connection.credentials.accessToken;
|
||||||
|
break;
|
||||||
|
case GitHubConnectionMethod.Pat:
|
||||||
|
token = connection.credentials.personalAccessToken;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
token = await getGitHubAppAuthToken(connection, gatewayService, gatewayV2Service);
|
||||||
|
}
|
||||||
|
|
||||||
const encryptedSecrets = await getEncryptedSecrets(secretSync, gatewayService, gatewayV2Service);
|
const encryptedSecrets = await getEncryptedSecrets(secretSync, gatewayService, gatewayV2Service);
|
||||||
const publicKey = await getPublicKey(secretSync, gatewayService, gatewayV2Service, token);
|
const publicKey = await getPublicKey(secretSync, gatewayService, gatewayV2Service, token);
|
||||||
@@ -269,10 +277,18 @@ export const GithubSyncFns = {
|
|||||||
const secretMap = Object.fromEntries(Object.entries(ogSecretMap).map(([i, v]) => [i.toUpperCase(), v]));
|
const secretMap = Object.fromEntries(Object.entries(ogSecretMap).map(([i, v]) => [i.toUpperCase(), v]));
|
||||||
|
|
||||||
const { connection } = secretSync;
|
const { connection } = secretSync;
|
||||||
const token =
|
let token: string;
|
||||||
connection.method === GitHubConnectionMethod.OAuth
|
|
||||||
? connection.credentials.accessToken
|
switch (connection.method) {
|
||||||
: await getGitHubAppAuthToken(connection, gatewayService, gatewayV2Service);
|
case GitHubConnectionMethod.OAuth:
|
||||||
|
token = connection.credentials.accessToken;
|
||||||
|
break;
|
||||||
|
case GitHubConnectionMethod.Pat:
|
||||||
|
token = connection.credentials.personalAccessToken;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
token = await getGitHubAppAuthToken(connection, gatewayService, gatewayV2Service);
|
||||||
|
}
|
||||||
|
|
||||||
const encryptedSecrets = await getEncryptedSecrets(secretSync, gatewayService, gatewayV2Service);
|
const encryptedSecrets = await getEncryptedSecrets(secretSync, gatewayService, gatewayV2Service);
|
||||||
|
|
||||||
|
|||||||
BIN
docs/images/app-connections/github/create-pat-form.png
Normal file
BIN
docs/images/app-connections/github/create-pat-form.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 294 KiB |
BIN
docs/images/app-connections/github/create-pat-method.png
Normal file
BIN
docs/images/app-connections/github/create-pat-method.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 343 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 211 KiB |
BIN
docs/images/app-connections/github/pat-connection.png
Normal file
BIN
docs/images/app-connections/github/pat-connection.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 154 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 147 KiB |
@@ -3,7 +3,7 @@ title: "GitHub Connection"
|
|||||||
description: "Learn how to configure a GitHub Connection for Infisical."
|
description: "Learn how to configure a GitHub Connection for Infisical."
|
||||||
---
|
---
|
||||||
|
|
||||||
Infisical supports two methods for connecting to GitHub.
|
Infisical supports three methods for connecting to GitHub.
|
||||||
|
|
||||||
<Tabs>
|
<Tabs>
|
||||||
<Tab title="GitHub App (Recommended)">
|
<Tab title="GitHub App (Recommended)">
|
||||||
@@ -178,5 +178,72 @@ Infisical supports two methods for connecting to GitHub.
|
|||||||
</Step>
|
</Step>
|
||||||
</Steps>
|
</Steps>
|
||||||
</Tab>
|
</Tab>
|
||||||
|
<Tab title="Personal Access Token">
|
||||||
|
Infisical will use a Personal Access Token to connect to GitHub.
|
||||||
|
|
||||||
|
## Create a Personal Access Token
|
||||||
|
<Steps>
|
||||||
|
<Step title="Create a Personal Access Token in GitHub">
|
||||||
|
Navigate to your user Settings > Developer settings > Personal Access Tokens to create a new Personal Access Token.
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
Click **Generate new token** to create the token.
|
||||||
|
</Step>
|
||||||
|
<Step title="Fill in the Personal Access Token details">
|
||||||
|
Fill in the Personal Access Token details:
|
||||||
|
- **Token name:** A descriptive name for the token (e.g., "infisical-connection-token")
|
||||||
|
- **Repository access:** Select the repositories you want to grant access to
|
||||||
|
- Select `All repositories` or `Only selected repositories` to be able to manage the secrets in the selected repositories.
|
||||||
|
- **Select scopes:** Add the following scopes:
|
||||||
|
- **Metadata**: Read-only
|
||||||
|
- **Environments**: Read and write
|
||||||
|
- **Secrets**: Read and write
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Click **Generate token** to create the token.
|
||||||
|
</Step>
|
||||||
|
<Step title="Copy the Personal Access Token">
|
||||||
|
Copy the generated token immediately as it won't be shown again.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<Warning>
|
||||||
|
Keep your Personal Access Token secure and do not share it. Anyone with access to this token can access your GitHub account and repositories.
|
||||||
|
</Warning>
|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
|
||||||
|
## Setup GitHub Connection in Infisical
|
||||||
|
|
||||||
|
<Steps>
|
||||||
|
<Step title="Navigate to App Connections">
|
||||||
|
Navigate to the **App Connections** page in the desired project.
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Add Connection">
|
||||||
|
Select the **GitHub Connection** option from the connection options modal.
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Authorize Connection">
|
||||||
|
Select the **Personal Access Token** method and fill in the **Personal Access Token** field with your Personal Access Token.
|
||||||
|
|
||||||
|
You may optionally configure GitHub Enterprise options:
|
||||||
|
- **Gateway:** The gateway connected to your private network
|
||||||
|
- **Hostname:** The hostname at which to access your GitHub Enterprise instance
|
||||||
|
|
||||||
|
Click **Create Connection**.
|
||||||
|
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
<Step title="Connection Created">
|
||||||
|
Your **GitHub Connection** is now available for use.
|
||||||
|

|
||||||
|
</Step>
|
||||||
|
</Steps>
|
||||||
|
</Tab>
|
||||||
|
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|||||||
@@ -135,6 +135,8 @@ export const getAppConnectionMethodDetails = (method: TAppConnection["method"])
|
|||||||
case GitHubConnectionMethod.App:
|
case GitHubConnectionMethod.App:
|
||||||
case GitHubRadarConnectionMethod.App:
|
case GitHubRadarConnectionMethod.App:
|
||||||
return { name: "GitHub App", icon: faGithub };
|
return { name: "GitHub App", icon: faGithub };
|
||||||
|
case GitHubConnectionMethod.Pat:
|
||||||
|
return { name: "Personal Access Token", icon: faKey };
|
||||||
case AzureKeyVaultConnectionMethod.OAuth:
|
case AzureKeyVaultConnectionMethod.OAuth:
|
||||||
case AzureAppConfigurationConnectionMethod.OAuth:
|
case AzureAppConfigurationConnectionMethod.OAuth:
|
||||||
case AzureClientSecretsConnectionMethod.OAuth:
|
case AzureClientSecretsConnectionMethod.OAuth:
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import { TRootAppConnection } from "@app/hooks/api/appConnections/types/root-con
|
|||||||
|
|
||||||
export enum GitHubConnectionMethod {
|
export enum GitHubConnectionMethod {
|
||||||
App = "github-app",
|
App = "github-app",
|
||||||
OAuth = "oauth"
|
OAuth = "oauth",
|
||||||
|
Pat = "pat"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TGitHubConnection = TRootAppConnection & { app: AppConnection.GitHub } & (
|
export type TGitHubConnection = TRootAppConnection & { app: AppConnection.GitHub } & (
|
||||||
@@ -24,4 +25,12 @@ export type TGitHubConnection = TRootAppConnection & { app: AppConnection.GitHub
|
|||||||
host?: string;
|
host?: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
method: GitHubConnectionMethod.Pat;
|
||||||
|
credentials: {
|
||||||
|
personalAccessToken: string;
|
||||||
|
instanceType?: "cloud" | "server";
|
||||||
|
host?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ const CreateForm = ({ app, onComplete, projectId }: CreateFormProps) => {
|
|||||||
case AppConnection.AWS:
|
case AppConnection.AWS:
|
||||||
return <AwsConnectionForm onSubmit={onSubmit} />;
|
return <AwsConnectionForm onSubmit={onSubmit} />;
|
||||||
case AppConnection.GitHub:
|
case AppConnection.GitHub:
|
||||||
return <GitHubConnectionForm projectId={projectId} />;
|
return <GitHubConnectionForm projectId={projectId} onSubmit={onSubmit} />;
|
||||||
case AppConnection.GitHubRadar:
|
case AppConnection.GitHubRadar:
|
||||||
return <GitHubRadarConnectionForm projectId={projectId} />;
|
return <GitHubRadarConnectionForm projectId={projectId} />;
|
||||||
case AppConnection.GCP:
|
case AppConnection.GCP:
|
||||||
@@ -213,7 +213,11 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => {
|
|||||||
return <AwsConnectionForm appConnection={appConnection} onSubmit={onSubmit} />;
|
return <AwsConnectionForm appConnection={appConnection} onSubmit={onSubmit} />;
|
||||||
case AppConnection.GitHub:
|
case AppConnection.GitHub:
|
||||||
return (
|
return (
|
||||||
<GitHubConnectionForm appConnection={appConnection} projectId={appConnection.projectId} />
|
<GitHubConnectionForm
|
||||||
|
appConnection={appConnection}
|
||||||
|
projectId={appConnection.projectId}
|
||||||
|
onSubmit={onSubmit}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
case AppConnection.GitHubRadar:
|
case AppConnection.GitHubRadar:
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
FormControl,
|
FormControl,
|
||||||
Input,
|
Input,
|
||||||
ModalClose,
|
ModalClose,
|
||||||
|
SecretInput,
|
||||||
Select,
|
Select,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
Tooltip
|
Tooltip
|
||||||
@@ -48,28 +49,58 @@ import {
|
|||||||
type Props = {
|
type Props = {
|
||||||
appConnection?: TGitHubConnection;
|
appConnection?: TGitHubConnection;
|
||||||
projectId: string | undefined | null;
|
projectId: string | undefined | null;
|
||||||
|
onSubmit: (formData: PatSchemaForm) => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const formSchema = genericAppConnectionFieldsSchema.extend({
|
const rootSchema = genericAppConnectionFieldsSchema.extend({
|
||||||
app: z.literal(AppConnection.GitHub),
|
app: z.literal(AppConnection.GitHub),
|
||||||
method: z.nativeEnum(GitHubConnectionMethod),
|
method: z.nativeEnum(GitHubConnectionMethod)
|
||||||
credentials: z
|
|
||||||
.union([
|
|
||||||
z.object({
|
|
||||||
instanceType: z.literal("cloud").optional(),
|
|
||||||
host: z.string().optional()
|
|
||||||
}),
|
|
||||||
z.object({
|
|
||||||
instanceType: z.literal("server"),
|
|
||||||
host: z.string().min(1, "Required")
|
|
||||||
})
|
|
||||||
])
|
|
||||||
.optional()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const baseCredentialsSchema = z.union([
|
||||||
|
z.object({
|
||||||
|
instanceType: z.literal("server"),
|
||||||
|
host: z.string().min(1, "Host is required for server instance type")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
instanceType: z.literal("cloud").optional(),
|
||||||
|
host: z.string().optional()
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
const appSchema = rootSchema.extend({
|
||||||
|
method: z.literal(GitHubConnectionMethod.App),
|
||||||
|
credentials: baseCredentialsSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
const oauthSchema = rootSchema.extend({
|
||||||
|
method: z.literal(GitHubConnectionMethod.OAuth),
|
||||||
|
credentials: baseCredentialsSchema
|
||||||
|
});
|
||||||
|
|
||||||
|
const patSchema = rootSchema.extend({
|
||||||
|
method: z.literal(GitHubConnectionMethod.Pat),
|
||||||
|
credentials: z.union([
|
||||||
|
z.object({
|
||||||
|
instanceType: z.literal("server"),
|
||||||
|
host: z.string().min(1, "Host is required for server instance type"),
|
||||||
|
personalAccessToken: z.string().min(1, "Personal Access Token is required")
|
||||||
|
}),
|
||||||
|
z.object({
|
||||||
|
instanceType: z.literal("cloud").optional(),
|
||||||
|
host: z.string().optional(),
|
||||||
|
personalAccessToken: z.string().min(1, "Personal Access Token is required")
|
||||||
|
})
|
||||||
|
])
|
||||||
|
});
|
||||||
|
|
||||||
|
type PatSchemaForm = z.infer<typeof patSchema>;
|
||||||
|
|
||||||
|
const formSchema = z.discriminatedUnion("method", [appSchema, oauthSchema, patSchema]);
|
||||||
|
|
||||||
type FormData = z.infer<typeof formSchema>;
|
type FormData = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
export const GitHubConnectionForm = ({ appConnection, projectId }: Props) => {
|
export const GitHubConnectionForm = ({ appConnection, projectId, onSubmit }: Props) => {
|
||||||
const isUpdate = Boolean(appConnection);
|
const isUpdate = Boolean(appConnection);
|
||||||
const [isRedirecting, setIsRedirecting] = useState(false);
|
const [isRedirecting, setIsRedirecting] = useState(false);
|
||||||
|
|
||||||
@@ -106,7 +137,12 @@ export const GitHubConnectionForm = ({ appConnection, projectId }: Props) => {
|
|||||||
|
|
||||||
const returnUrl = useGetAppConnectionOauthReturnUrl();
|
const returnUrl = useGetAppConnectionOauthReturnUrl();
|
||||||
|
|
||||||
const onSubmit = (formData: FormData) => {
|
const submitHandler = async (formData: FormData) => {
|
||||||
|
if (formData.method === GitHubConnectionMethod.Pat) {
|
||||||
|
await onSubmit(formData);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setIsRedirecting(true);
|
setIsRedirecting(true);
|
||||||
const state = crypto.randomBytes(16).toString("hex");
|
const state = crypto.randomBytes(16).toString("hex");
|
||||||
localStorage.setItem("latestCSRFToken", state);
|
localStorage.setItem("latestCSRFToken", state);
|
||||||
@@ -151,15 +187,26 @@ export const GitHubConnectionForm = ({ appConnection, projectId }: Props) => {
|
|||||||
case GitHubConnectionMethod.App:
|
case GitHubConnectionMethod.App:
|
||||||
isMissingConfig = !appClientSlug;
|
isMissingConfig = !appClientSlug;
|
||||||
break;
|
break;
|
||||||
|
case GitHubConnectionMethod.Pat:
|
||||||
|
isMissingConfig = false;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unhandled GitHub Connection method: ${selectedMethod}`);
|
throw new Error(`Unhandled GitHub Connection method: ${selectedMethod}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const methodDetails = getAppConnectionMethodDetails(selectedMethod);
|
const methodDetails = getAppConnectionMethodDetails(selectedMethod);
|
||||||
|
|
||||||
|
const getButtonText = () => {
|
||||||
|
if (selectedMethod === GitHubConnectionMethod.Pat) {
|
||||||
|
return isUpdate ? "Update Connection" : "Create Connection";
|
||||||
|
}
|
||||||
|
|
||||||
|
return isUpdate ? "Reconnect to GitHub" : "Connect to GitHub";
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormProvider {...form}>
|
<FormProvider {...form}>
|
||||||
<form onSubmit={handleSubmit(onSubmit)}>
|
<form onSubmit={handleSubmit(submitHandler)}>
|
||||||
{!isUpdate && <GenericAppConnectionsFields />}
|
{!isUpdate && <GenericAppConnectionsFields />}
|
||||||
<Controller
|
<Controller
|
||||||
name="method"
|
name="method"
|
||||||
@@ -201,6 +248,26 @@ export const GitHubConnectionForm = ({ appConnection, projectId }: Props) => {
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
{selectedMethod === GitHubConnectionMethod.Pat && (
|
||||||
|
<Controller
|
||||||
|
name="credentials.personalAccessToken"
|
||||||
|
control={control}
|
||||||
|
shouldUnregister
|
||||||
|
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error?.message)}
|
||||||
|
label="Personal Access Token"
|
||||||
|
>
|
||||||
|
<SecretInput
|
||||||
|
containerClassName="text-gray-400 group-focus-within:!border-primary-400/50 border border-mineshaft-500 bg-mineshaft-900 px-2.5 py-1.5"
|
||||||
|
value={value}
|
||||||
|
onChange={(e) => onChange(e.target.value)}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<Accordion type="single" collapsible className="w-full">
|
<Accordion type="single" collapsible className="w-full">
|
||||||
<AccordionItem value="enterprise-options" className="data-[state=open]:border-none">
|
<AccordionItem value="enterprise-options" className="data-[state=open]:border-none">
|
||||||
<AccordionTrigger className="h-fit flex-none pl-1 text-sm">
|
<AccordionTrigger className="h-fit flex-none pl-1 text-sm">
|
||||||
@@ -310,7 +377,7 @@ export const GitHubConnectionForm = ({ appConnection, projectId }: Props) => {
|
|||||||
isLoading={isSubmitting || isRedirecting}
|
isLoading={isSubmitting || isRedirecting}
|
||||||
isDisabled={isSubmitting || (!isUpdate && !isDirty) || isMissingConfig || isRedirecting}
|
isDisabled={isSubmitting || (!isUpdate && !isDirty) || isMissingConfig || isRedirecting}
|
||||||
>
|
>
|
||||||
{isUpdate ? "Reconnect to GitHub" : "Connect to GitHub"}
|
{getButtonText()}
|
||||||
</Button>
|
</Button>
|
||||||
<ModalClose asChild>
|
<ModalClose asChild>
|
||||||
<Button colorSchema="secondary" variant="plain">
|
<Button colorSchema="secondary" variant="plain">
|
||||||
|
|||||||
Reference in New Issue
Block a user