feat(secret-sync): Add gitlab secret sync

This commit is contained in:
carlosmonastyrski
2025-06-20 21:13:14 -03:00
parent e5352e7aa8
commit e201d77a8f
99 changed files with 2912 additions and 56 deletions
@@ -0,0 +1,14 @@
import { TGitlabSync } from "@app/hooks/api/secretSyncs/types/gitlab-sync";
import { getSecretSyncDestinationColValues } from "../helpers";
import { SecretSyncTableCell } from "../SecretSyncTableCell";
type Props = {
secretSync: TGitlabSync;
};
export const GitLabSyncDestinationCol = ({ secretSync }: Props) => {
const { primaryText, secondaryText } = getSecretSyncDestinationColValues(secretSync);
return <SecretSyncTableCell primaryText={primaryText} secondaryText={secondaryText} />;
};
@@ -11,6 +11,7 @@ import { DatabricksSyncDestinationCol } from "./DatabricksSyncDestinationCol";
import { FlyioSyncDestinationCol } from "./FlyioSyncDestinationCol";
import { GcpSyncDestinationCol } from "./GcpSyncDestinationCol";
import { GitHubSyncDestinationCol } from "./GitHubSyncDestinationCol";
import { GitLabSyncDestinationCol } from "./GitLabSyncDestinationCol";
import { HCVaultSyncDestinationCol } from "./HCVaultSyncDestinationCol";
import { HerokuSyncDestinationCol } from "./HerokuSyncDestinationCol";
import { HumanitecSyncDestinationCol } from "./HumanitecSyncDestinationCol";
@@ -67,6 +68,8 @@ export const SecretSyncDestinationCol = ({ secretSync }: Props) => {
return <RenderSyncDestinationCol secretSync={secretSync} />;
case SecretSync.Flyio:
return <FlyioSyncDestinationCol secretSync={secretSync} />;
case SecretSync.Gitlab:
return <GitLabSyncDestinationCol secretSync={secretSync} />;
default:
throw new Error(
`Unhandled Secret Sync Destination Col: ${(secretSync as TSecretSync).destination}`
@@ -128,6 +128,10 @@ export const getSecretSyncDestinationColValues = (secretSync: TSecretSync) => {
primaryText = destinationConfig.appId;
secondaryText = "App ID";
break;
case SecretSync.Gitlab:
primaryText = destinationConfig.projectName;
secondaryText = destinationConfig.projectId;
break;
default:
throw new Error(`Unhandled Destination Col Values ${destination}`);
}
@@ -0,0 +1,34 @@
import { GenericFieldLabel } from "@app/components/secret-syncs";
import { TGitlabSync } from "@app/hooks/api/secretSyncs/types/gitlab-sync";
type Props = {
secretSync: TGitlabSync;
};
export const GitLabSyncDestinationSection = ({ secretSync }: Props) => {
const {
destinationConfig: {
projectName,
projectId,
targetEnvironment,
shouldProtectSecrets,
shouldMaskSecrets,
shouldHideSecrets
}
} = secretSync;
return (
<>
<GenericFieldLabel label="Project Name">{projectName}</GenericFieldLabel>
<GenericFieldLabel label="Project ID">{projectId}</GenericFieldLabel>
{targetEnvironment && (
<GenericFieldLabel label="Environment">{targetEnvironment}</GenericFieldLabel>
)}
<GenericFieldLabel label="Protect Secrets">
{shouldProtectSecrets ? "Yes" : "No"}
</GenericFieldLabel>
<GenericFieldLabel label="Mask Secrets">{shouldMaskSecrets ? "Yes" : "No"}</GenericFieldLabel>
<GenericFieldLabel label="Hide Secrets">{shouldHideSecrets ? "Yes" : "No"}</GenericFieldLabel>
</>
);
};
@@ -22,6 +22,7 @@ import { DatabricksSyncDestinationSection } from "./DatabricksSyncDestinationSec
import { FlyioSyncDestinationSection } from "./FlyioSyncDestinationSection";
import { GcpSyncDestinationSection } from "./GcpSyncDestinationSection";
import { GitHubSyncDestinationSection } from "./GitHubSyncDestinationSection";
import { GitLabSyncDestinationSection } from "./GitLabSyncDestinationSection";
import { HCVaultSyncDestinationSection } from "./HCVaultSyncDestinationSection";
import { HerokuSyncDestinationSection } from "./HerokuSyncDestinationSection";
import { HumanitecSyncDestinationSection } from "./HumanitecSyncDestinationSection";
@@ -106,6 +107,9 @@ export const SecretSyncDestinationSection = ({ secretSync, onEditDestination }:
case SecretSync.Flyio:
DestinationComponents = <FlyioSyncDestinationSection secretSync={secretSync} />;
break;
case SecretSync.Gitlab:
DestinationComponents = <GitLabSyncDestinationSection secretSync={secretSync} />;
break;
default:
throw new Error(`Unhandled Destination Section components: ${destination}`);
}
@@ -58,6 +58,7 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) =
case SecretSync.Heroku:
case SecretSync.Render:
case SecretSync.Flyio:
case SecretSync.Gitlab:
AdditionalSyncOptionsComponent = null;
break;
default:
@@ -3,11 +3,13 @@ import { useNavigate, useSearch } from "@tanstack/react-router";
import { ROUTE_PATHS } from "@app/const/routes";
import { useWorkspace } from "@app/context";
import { useAuthorizeIntegration } from "@app/hooks/api";
import { useCreateAppConnection, useUpdateAppConnection } from "@app/hooks/api/appConnections";
import { GitlabConnectionMethod } from "@app/hooks/api/appConnections/types/gitlab-connection";
export const GitLabOAuthCallbackPage = () => {
const navigate = useNavigate();
const { mutateAsync } = useAuthorizeIntegration();
const { mutateAsync: createAppConnection } = useCreateAppConnection();
const { mutateAsync: updateAppConnection } = useUpdateAppConnection();
const { code, state } = useSearch({
from: ROUTE_PATHS.SecretManager.Integratons.GitlabOauthCallbackPage.id
@@ -17,37 +19,83 @@ export const GitLabOAuthCallbackPage = () => {
useEffect(() => {
(async () => {
try {
// validate state
const [csrfToken, url] = (state as string).split("|", 2);
// Validate CSRF state token
const [csrfToken] = (state as string).split("|", 2);
const storedState = localStorage.getItem("latestCSRFToken");
if (csrfToken !== storedState) {
console.error("CSRF token mismatch");
navigate({
to: "/organization/app-connections",
search: { error: "invalid_state" }
});
return;
}
if (csrfToken !== localStorage.getItem("latestCSRFToken")) return;
localStorage.removeItem("latestCSRFToken");
const integrationAuth = await mutateAsync({
workspaceId: currentWorkspace.id,
code: code as string,
integration: "gitlab",
...(url === ""
? {}
: {
url
})
});
// Retrieve stored form dataAdd commentMore actions
const storedFormData = localStorage.getItem("gitlabConnectionFormData");
if (!storedFormData) {
console.error("No stored form data found");
navigate({
to: "/organization/app-connections",
search: { error: "missing_form_data" }
});
return;
}
const formData = JSON.parse(storedFormData);
localStorage.removeItem("gitlabConnectionFormData");
// Prepare app connection data with OAuth credentials
const connectionData = {
...formData,
method: GitlabConnectionMethod.OAuth,
credentials: {
code: code as string
}
};
let appConnection;
// Create or update app connection
if (formData.isUpdate && formData.connectionId) {
appConnection = await updateAppConnection({
connectionId: formData.connectionId,
...connectionData
});
} else {
appConnection = await createAppConnection({
workspaceId: currentWorkspace.id,
...connectionData
});
}
// Navigate to success page or app connections list
navigate({
to: "/secret-manager/$projectId/integrations/gitlab/create",
params: {
projectId: currentWorkspace.id
},
to: "/organization/app-connections",
search: {
integrationAuthId: integrationAuth.id
success: formData.isUpdate ? "connection_updated" : "connection_created",
connectionId: appConnection.id
}
});
} catch (err) {
console.error(err);
console.error("Error handling GitLab OAuth callback:", err);
navigate({
to: "/organization/app-connections",
search: { error: "connection_failed" }
});
}
})();
}, []);
}, [code, state, navigate, createAppConnection, updateAppConnection, currentWorkspace.id]);
return <div />;
return (
<div className="flex h-screen items-center justify-center">
<div className="text-center">
<div className="mx-auto mb-4 h-8 w-8 animate-spin rounded-full border-b-2 border-primary" />
<p className="text-gray-600">Connecting to GitLab...</p>
</div>
</div>
);
};