mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Improve github manual sync message and docs
This commit is contained in:
@@ -137,7 +137,6 @@ export const registerGithubOrgSyncRouter = async (server: FastifyZodProvider) =>
|
||||
schema: {
|
||||
response: {
|
||||
200: z.object({
|
||||
syncedUsersCount: z.number(),
|
||||
totalUsers: z.number(),
|
||||
errors: z.array(z.string()),
|
||||
createdTeams: z.array(z.string()),
|
||||
@@ -153,7 +152,6 @@ export const registerGithubOrgSyncRouter = async (server: FastifyZodProvider) =>
|
||||
});
|
||||
|
||||
return {
|
||||
syncedUsersCount: result.syncedUsersCount,
|
||||
totalUsers: result.totalUsers,
|
||||
errors: result.errors,
|
||||
createdTeams: result.createdTeams,
|
||||
|
||||
@@ -553,7 +553,6 @@ export const githubOrgSyncServiceFactory = ({
|
||||
) as OrgMembershipWithUser[];
|
||||
|
||||
const startTime = Date.now();
|
||||
let syncedUsersCount = 0;
|
||||
const syncErrors: string[] = [];
|
||||
|
||||
const octokit = new OctokitWithPlugin({
|
||||
@@ -683,8 +682,6 @@ export const githubOrgSyncServiceFactory = ({
|
||||
const updatedTeams = new Set<string>();
|
||||
const totalRemovedMemberships = 0;
|
||||
|
||||
syncedUsersCount = allGithubUsernamesInTeams.size;
|
||||
|
||||
await groupDAL.transaction(async (tx) => {
|
||||
if (teamsToCreate.length > 0) {
|
||||
const newGroups = await groupDAL.insertMany(
|
||||
@@ -810,7 +807,6 @@ export const githubOrgSyncServiceFactory = ({
|
||||
);
|
||||
|
||||
return {
|
||||
syncedUsersCount,
|
||||
totalUsers: activeMembers.length,
|
||||
errors: syncErrors,
|
||||
createdTeams: Array.from(createdTeams),
|
||||
|
||||
@@ -27,7 +27,6 @@ export interface TSyncAllTeamsDTO {
|
||||
}
|
||||
|
||||
export interface TSyncResult {
|
||||
syncedUsersCount: number;
|
||||
totalUsers: number;
|
||||
errors: string[];
|
||||
createdTeams: string[];
|
||||
|
||||
@@ -49,17 +49,59 @@ When a user logs in via the GitHub OAuth flow and selects the configured organiz
|
||||
|
||||
You can manually synchronize GitHub teams for all organization members who have previously logged in with GitHub. This bulk sync operation updates team memberships without requiring users to log in again.
|
||||
|
||||
To trigger a manual sync, click the **Manually sync GitHub teams for all organization members** button:
|
||||
<Steps>
|
||||
<Step title="Generate a GitHub Access Token">
|
||||
To perform manual syncs, you'll need to create a GitHub Personal Access Token with the appropriate permissions. GitHub offers two types of tokens:
|
||||
|
||||

|
||||
<Tabs>
|
||||
<Tab title="Classic Token">
|
||||
1. Go to [GitHub Settings → Personal Access Tokens → Tokens (classic)](https://github.com/settings/tokens)
|
||||
2. Click **Generate new token** → **Generate new token (classic)**
|
||||
3. Give your token a descriptive name (e.g., "Infisical GitHub Sync")
|
||||
4. Set an appropriate expiration date
|
||||
5. Select the **read:org** scope - Required to read organization team information
|
||||
6. Click **Generate token**
|
||||
7. Copy the token immediately (you won't be able to see it again)
|
||||
|
||||
If you don't have a GitHub token configured or your token has expired, a modal will appear prompting you to provide one:
|
||||

|
||||
</Tab>
|
||||
<Tab title="Fine-grained Token">
|
||||
1. Go to [GitHub Settings → Personal Access Tokens → Fine-grained tokens](https://github.com/settings/personal-access-tokens/new)
|
||||
2. Click **Generate new token**
|
||||
3. Give your token a descriptive name (e.g., "Infisical GitHub Sync")
|
||||
4. Set an appropriate expiration date
|
||||
5. Select your organization under **Resource owner**
|
||||
6. Under **Organization permissions**, set **Members** to **Read**
|
||||
7. Click **Generate token**
|
||||
8. Copy the token immediately (you won't be able to see it again)
|
||||
|
||||

|
||||

|
||||
</Tab>
|
||||
</Tabs>
|
||||
</Step>
|
||||
|
||||
<Step title="Configure the Token in Infisical">
|
||||
1. Navigate to the **Single Sign-On (SSO)** page and select the **Provisioning** tab.
|
||||
2. Click the **Configure** button next to your GitHub Organization configuration.
|
||||
3. In the configuration modal, you'll find an optional **GitHub Access Token** field.
|
||||
4. Paste the token you generated in the previous step.
|
||||
5. Click **Update** to save the configuration.
|
||||
|
||||
<Info>
|
||||
The token must be granted **read:org** and **read:user** permissions to successfully sync groups. This configuration is only required once - if you already have a valid token configured, the sync will proceed automatically.
|
||||
</Info>
|
||||

|
||||
</Step>
|
||||
|
||||
<Step title="Perform Manual Sync">
|
||||
Once you have configured the GitHub access token:
|
||||
|
||||
1. Navigate to the **Single Sign-On (SSO)** page and select the **Provisioning** tab.
|
||||
2. You'll see a **Sync Now** section with a button to trigger the manual sync.
|
||||
3. Click **Sync Now** to synchronize GitHub teams for all organization members.
|
||||
|
||||

|
||||
|
||||
The sync operation will process all organization members who have previously logged in with GitHub and update their team memberships accordingly.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
||||
BIN
docs/images/platform/external-syncs/github-classic-token.png
Normal file
BIN
docs/images/platform/external-syncs/github-classic-token.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 506 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 252 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 704 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 496 KiB |
@@ -44,7 +44,6 @@ export const useDeleteGithubSyncOrgConfig = () => {
|
||||
export const useSyncAllGithubTeams = () => {
|
||||
return useMutation({
|
||||
mutationFn: async (): Promise<{
|
||||
syncedUsersCount: number;
|
||||
totalUsers: number;
|
||||
errors: string[];
|
||||
createdTeams: string[];
|
||||
|
||||
@@ -51,7 +51,7 @@ export const GithubOrgSyncConfigModal = ({
|
||||
formState: { isSubmitting }
|
||||
} = useForm<FormData>({
|
||||
resolver: zodResolver(schema),
|
||||
values: data ? { githubOrgName: data.githubOrgName } : undefined
|
||||
values: data ? { githubOrgName: data.githubOrgName, githubOrgAccessToken: "" } : undefined
|
||||
});
|
||||
|
||||
const onFormSubmit = async ({ githubOrgName, githubOrgAccessToken }: FormData) => {
|
||||
@@ -123,21 +123,21 @@ export const GithubOrgSyncConfigModal = ({
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
{/* <Controller
|
||||
<Controller
|
||||
control={control}
|
||||
name="githubOrgAccessToken"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="GitHub Org Scoped Access Token"
|
||||
label="GitHub Access Token"
|
||||
isError={Boolean(error)}
|
||||
isOptional
|
||||
errorText={error?.message}
|
||||
helperText="A GitHub access token is required only for private organizations. It will not be visible after saving."
|
||||
helperText="Required for manual sync operations. The token must have 'read:org' permissions."
|
||||
>
|
||||
<Input {...field} placeholder="example" />
|
||||
<Input {...field} type="password" placeholder="ghp_xxxxxxxxxxxx" />
|
||||
</FormControl>
|
||||
)}
|
||||
/> */}
|
||||
/>
|
||||
<div className="flex gap-8 pt-4">
|
||||
<Button type="submit" isLoading={isSubmitting} isDisabled={isSubmitting}>
|
||||
{isUpdate ? "Update" : "Configure"}
|
||||
|
||||
@@ -1,21 +1,9 @@
|
||||
import { useState } from "react";
|
||||
import { faCircleCheck, faCircleXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
|
||||
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
Input,
|
||||
Modal,
|
||||
ModalContent,
|
||||
Skeleton,
|
||||
Spinner,
|
||||
Switch
|
||||
} from "@app/components/v2";
|
||||
import { Button, Modal, ModalContent, Skeleton, Spinner, Switch } from "@app/components/v2";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects, useSubscription } from "@app/context";
|
||||
import {
|
||||
githubOrgSyncConfigQueryKeys,
|
||||
@@ -31,22 +19,9 @@ export const OrgGithubSyncSection = () => {
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
|
||||
"upgradePlan",
|
||||
"githubOrgSyncConfig",
|
||||
"deleteGithubOrgSyncConfig",
|
||||
"setAccessToken"
|
||||
"deleteGithubOrgSyncConfig"
|
||||
] as const);
|
||||
|
||||
const [accessToken, setAccessToken] = useState("");
|
||||
const [tokenValidationResult, setTokenValidationResult] = useState<{
|
||||
valid: boolean;
|
||||
organizationInfo?: {
|
||||
id: number;
|
||||
login: string;
|
||||
name: string;
|
||||
publicRepos?: number;
|
||||
privateRepos?: number;
|
||||
};
|
||||
} | null>(null);
|
||||
|
||||
const githubOrgSyncConfig = useQuery({
|
||||
...githubOrgSyncConfigQueryKeys.get(),
|
||||
enabled: subscription.githubOrgSync,
|
||||
@@ -62,7 +37,7 @@ export const OrgGithubSyncSection = () => {
|
||||
const handleBulkSync = async () => {
|
||||
try {
|
||||
const result = await syncAllTeamsMutation.mutateAsync();
|
||||
let message = `Successfully synced teams for ${result.syncedUsersCount} user${result.syncedUsersCount === 1 ? "" : "s"}`;
|
||||
let message = "Successfully synced teams";
|
||||
|
||||
const details = [];
|
||||
if (result.createdTeams.length > 0) {
|
||||
@@ -108,9 +83,8 @@ export const OrgGithubSyncSection = () => {
|
||||
errorMessage.includes("expired") ||
|
||||
errorMessage.includes("set a token first"))
|
||||
) {
|
||||
handlePopUpOpen("setAccessToken");
|
||||
createNotification({
|
||||
text: "Please provide a GitHub access token to continue with the sync",
|
||||
text: "Please set a GitHub access token in the configuration modal to continue with the sync",
|
||||
type: "error"
|
||||
});
|
||||
} else {
|
||||
@@ -122,41 +96,6 @@ export const OrgGithubSyncSection = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSetAccessToken = async () => {
|
||||
if (!accessToken.trim()) {
|
||||
createNotification({
|
||||
text: "Please enter a GitHub access token",
|
||||
type: "error"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await updateGithubSyncOrgConfig.mutateAsync({
|
||||
githubOrgAccessToken: accessToken.trim()
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: "GitHub access token set successfully. Starting sync...",
|
||||
type: "success"
|
||||
});
|
||||
|
||||
setAccessToken("");
|
||||
handlePopUpToggle("setAccessToken", false);
|
||||
|
||||
// Automatically trigger sync after token is set
|
||||
await handleBulkSync();
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
(error as any)?.response?.data?.message || (error as Error)?.message || "Unknown error";
|
||||
|
||||
createNotification({
|
||||
text: `Failed to set GitHub access token: ${errorMessage}`,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-4 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-6">
|
||||
<p className="text-xl font-semibold text-gray-200">
|
||||
@@ -265,79 +204,6 @@ export const OrgGithubSyncSection = () => {
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
text="You can use GitHub Organization Plan if you switch to Infisical's Enterprise plan."
|
||||
/>
|
||||
<Modal
|
||||
isOpen={popUp?.setAccessToken?.isOpen}
|
||||
onOpenChange={(isOpen) => {
|
||||
handlePopUpToggle("setAccessToken", isOpen);
|
||||
if (!isOpen) {
|
||||
setAccessToken("");
|
||||
setTokenValidationResult(null);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ModalContent
|
||||
title="GitHub Access Token Required"
|
||||
subTitle="Provide a GitHub access token to sync teams from your GitHub organization"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<FormControl
|
||||
label="GitHub Access Token"
|
||||
tooltipText="The provided token must be granted read:org and read:user permissions in order to successfully sync groups"
|
||||
tooltipClassName="max-w-md"
|
||||
>
|
||||
<div className="relative">
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="ghp_xxxxxxxxxxxx"
|
||||
value={accessToken}
|
||||
onChange={(e) => {
|
||||
setAccessToken(e.target.value);
|
||||
if (tokenValidationResult) {
|
||||
setTokenValidationResult(null);
|
||||
}
|
||||
}}
|
||||
autoComplete="off"
|
||||
/>
|
||||
{tokenValidationResult && (
|
||||
<div className="absolute right-3 top-1/2 -translate-y-1/2">
|
||||
{tokenValidationResult.valid ? (
|
||||
<FontAwesomeIcon icon={faCircleCheck} size="xs" className="text-green-500" />
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faCircleXmark} size="xs" className="text-red-500" />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</FormControl>
|
||||
<div className="flex justify-between">
|
||||
<div className="flex space-x-2">
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
onClick={() => {
|
||||
handlePopUpToggle("setAccessToken", false);
|
||||
setAccessToken("");
|
||||
setTokenValidationResult(null);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
onClick={handleSetAccessToken}
|
||||
isLoading={updateGithubSyncOrgConfig.isPending || syncAllTeamsMutation.isPending}
|
||||
isDisabled={
|
||||
!accessToken.trim() ||
|
||||
updateGithubSyncOrgConfig.isPending ||
|
||||
syncAllTeamsMutation.isPending
|
||||
}
|
||||
>
|
||||
Set Token & Sync
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user