mirror of
https://github.com/multica-ai/multica.git
synced 2026-08-12 19:06:06 +02:00
* feat(vcs): gate self-hosted Git providers to self-host deployments only (MUL-3772) The Forgejo/Gitea/GitLab integration is intended for self-hosted Multica, where Multica can reach a Git instance on the operator's own network. On the managed multi-tenant cloud it adds an SSRF surface (connect validates a user-supplied instance URL from the server) and would store third-party Git tokens for all tenants under one key, while only serving the small subset of users whose instance is publicly reachable. Product decision: offer it on self-host only. - Add an explicit deployment switch MULTICA_VCS_INTEGRATION_ENABLED (default off). Connect, rotate, and webhook now require BOTH the switch on AND a valid MULTICA_VCS_SECRET_KEY — the switch is the product boundary, not key presence alone. When off, connect/rotate return 404 and the webhook returns a bare 404 (no config leak), independent of the frontend. - /api/config exposes vcs_integration_available (mirrors the switch, omitted when false) so the Settings UI hides the whole "Git providers" section on cloud instead of surfacing an operator-only "missing key" hint. - docker-compose.selfhost.yml defaults the switch on; .env.example documents it. - Docs (en/zh) lead with a callout: available on self-hosted Multica only, not Multica Cloud, and clarify "self-hosted" means Multica itself, not just Git. #5006 / #5883 stay in place — the schema and backend capability are retained; this only gates availability. No cloud VCS connection can exist (connect always required the key, which the cloud never set), so nothing needs migrating. Verified: go build/vet + VCS/config handler tests on a fresh migrated DB (incl. a new disabled-deployment 404 test); pnpm typecheck (core + views) and the integrations-tab + core schema/config vitest suites pass. Co-authored-by: multica-agent <github@multica.ai> * fix(vcs): complete self-host integration gating (MUL-5138) Co-authored-by: multica-agent <github@multica.ai> --------- Co-authored-by: Bohan-J <bohan@devv.ai> Co-authored-by: multica-agent <github@multica.ai>
62 lines
2.6 KiB
TypeScript
62 lines
2.6 KiB
TypeScript
"use client";
|
|
|
|
import { useQuery } from "@tanstack/react-query";
|
|
import { LarkTab } from "./lark-tab";
|
|
import { ComposioTab } from "./composio-tab";
|
|
import { SlackTab } from "./slack-tab";
|
|
import { VCSTab } from "./vcs-tab";
|
|
import { ApiError } from "@multica/core/api";
|
|
import { composioToolkitsOptions } from "@multica/core/composio";
|
|
import { useConfigStore, useFeatureEnabled } from "@multica/core/config";
|
|
import { COMPOSIO_MCP_APPS_FLAG } from "@multica/core/feature-flags";
|
|
import { useT } from "../../i18n";
|
|
import { SettingsSection, SettingsTab } from "./settings-layout";
|
|
|
|
// Integrations is the umbrella tab for third-party platform connections.
|
|
// GitHub has its own top-level tab (see github-tab.tsx); everything else
|
|
// — currently Lark, Composio, Slack, and the self-hosted Git providers (Forgejo /
|
|
// Gitea / GitLab), with Linear etc. to follow — lives in here under its own
|
|
// section heading so additional integrations slot in without changing the IA.
|
|
// IntegrationsTab is just the host; each integration owns its own description
|
|
// and install flow.
|
|
export function IntegrationsTab() {
|
|
const { t } = useT("settings");
|
|
|
|
const composioEnabled = useFeatureEnabled(COMPOSIO_MCP_APPS_FLAG, false);
|
|
// Composio is hidden entirely until the feature is enabled and a key is
|
|
// configured server-side. A 503 from the toolkits endpoint means the server
|
|
// withheld the integration despite the frontend flag being on.
|
|
const composioToolkits = useQuery({
|
|
...composioToolkitsOptions(),
|
|
enabled: composioEnabled,
|
|
});
|
|
const composioUnconfigured =
|
|
composioToolkits.error instanceof ApiError && composioToolkits.error.status === 503;
|
|
|
|
// Self-host-only integration: the managed cloud reports this false (field
|
|
// omitted from /api/config), so the whole section — header included — is
|
|
// hidden there rather than showing an operator-only "missing key" message.
|
|
const vcsAvailable = useConfigStore((s) => s.vcsIntegrationAvailable);
|
|
|
|
return (
|
|
<SettingsTab title={t(($) => $.page.tabs.integrations)}>
|
|
<SettingsSection title={t(($) => $.lark.section_title)}>
|
|
<LarkTab />
|
|
</SettingsSection>
|
|
{composioEnabled && !composioUnconfigured && (
|
|
<SettingsSection title={t(($) => $.composio.section_title)}>
|
|
<ComposioTab />
|
|
</SettingsSection>
|
|
)}
|
|
<SettingsSection title={t(($) => $.slack.section_title)}>
|
|
<SlackTab />
|
|
</SettingsSection>
|
|
{vcsAvailable && (
|
|
<SettingsSection title={t(($) => $.vcs.section_title)}>
|
|
<VCSTab />
|
|
</SettingsSection>
|
|
)}
|
|
</SettingsTab>
|
|
);
|
|
}
|