mirror of
https://github.com/multica-ai/multica.git
synced 2026-08-11 16:26:02 +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>
86 lines
3.2 KiB
TypeScript
86 lines
3.2 KiB
TypeScript
import { createStore } from "zustand/vanilla";
|
|
import { useStore } from "zustand";
|
|
|
|
interface ConfigState {
|
|
cdnDomain: string;
|
|
// True when cdnDomain serves private content via time-bounded signed URLs
|
|
// (CloudFront signing enabled server-side). Renderers must not treat a raw
|
|
// storage URL on that domain as a loadable media source (MUL-3254).
|
|
cdnSigned: boolean;
|
|
allowSignup: boolean;
|
|
googleClientId: string;
|
|
daemonServerUrl: string;
|
|
daemonAppUrl: string;
|
|
// Self-host gate (#3433): when true, every "Create workspace" affordance
|
|
// must be hidden. Defaults to false so unknown / older servers behave like
|
|
// the managed-cloud case.
|
|
workspaceCreationDisabled: boolean;
|
|
// Self-host-only gate for the Git provider integration (Forgejo / Gitea /
|
|
// GitLab). When false the whole Settings → Integrations "Git providers"
|
|
// section is hidden. Defaults to false so unknown / older servers and the
|
|
// managed cloud (which omits the field) keep it hidden.
|
|
vcsIntegrationAvailable: boolean;
|
|
featureFlags: Record<string, boolean>;
|
|
// The running API build version, surfaced in the Help popover so
|
|
// self-hosted operators can confirm what's deployed. Empty for dev builds
|
|
// or servers older than this feature.
|
|
serverVersion: string;
|
|
setCdnConfig: (config: { cdnDomain: string; cdnSigned?: boolean }) => void;
|
|
setAuthConfig: (config: {
|
|
allowSignup: boolean;
|
|
googleClientId?: string;
|
|
workspaceCreationDisabled?: boolean;
|
|
vcsIntegrationAvailable?: boolean;
|
|
}) => void;
|
|
setDaemonConfig: (config: {
|
|
daemonServerUrl?: string;
|
|
daemonAppUrl?: string;
|
|
}) => void;
|
|
setFeatureFlags: (flags?: Record<string, boolean>) => void;
|
|
setServerVersion: (version?: string) => void;
|
|
}
|
|
|
|
export const configStore = createStore<ConfigState>((set) => ({
|
|
cdnDomain: "",
|
|
cdnSigned: false,
|
|
allowSignup: true,
|
|
googleClientId: "",
|
|
daemonServerUrl: "",
|
|
daemonAppUrl: "",
|
|
workspaceCreationDisabled: false,
|
|
vcsIntegrationAvailable: false,
|
|
featureFlags: {},
|
|
serverVersion: "",
|
|
setCdnConfig: ({ cdnDomain, cdnSigned = false }) => set({ cdnDomain, cdnSigned }),
|
|
setAuthConfig: ({
|
|
allowSignup,
|
|
googleClientId = "",
|
|
workspaceCreationDisabled = false,
|
|
vcsIntegrationAvailable = false,
|
|
}) => set({ allowSignup, googleClientId, workspaceCreationDisabled, vcsIntegrationAvailable }),
|
|
setDaemonConfig: ({ daemonServerUrl = "", daemonAppUrl = "" }) =>
|
|
set({ daemonServerUrl, daemonAppUrl }),
|
|
setFeatureFlags: (flags = {}) => set({ featureFlags: { ...flags } }),
|
|
setServerVersion: (version = "") => set({ serverVersion: version }),
|
|
}));
|
|
|
|
export function useConfigStore(): ConfigState;
|
|
export function useConfigStore<T>(selector: (state: ConfigState) => T): T;
|
|
export function useConfigStore<T>(selector?: (state: ConfigState) => T) {
|
|
return useStore(configStore, selector as (state: ConfigState) => T);
|
|
}
|
|
|
|
export function featureFlagEnabled(
|
|
flags: Readonly<Record<string, boolean>> | undefined,
|
|
key: string,
|
|
defaultValue = false,
|
|
): boolean {
|
|
return flags?.[key] ?? defaultValue;
|
|
}
|
|
|
|
export function useFeatureEnabled(key: string, defaultValue = false): boolean {
|
|
return useConfigStore((state) =>
|
|
featureFlagEnabled(state.featureFlags, key, defaultValue),
|
|
);
|
|
}
|