From b3d53846cff995a56beec8ce21e31c7bd1e5f442 Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Mon, 3 Nov 2025 13:17:29 +0100 Subject: [PATCH 1/7] basic proxy --- .github/workflows/production.yaml | 3 ++ .github/workflows/release.yaml | 3 ++ bun.lock | 6 +-- integrations/gitlab/gitbook-manifest.yaml | 3 ++ integrations/gitlab/src/api.ts | 65 +++++++++++++++++------ integrations/gitlab/src/components.tsx | 4 +- integrations/gitlab/src/index.ts | 9 ++-- integrations/gitlab/src/installation.ts | 3 +- integrations/gitlab/src/provider.ts | 10 ++-- integrations/gitlab/src/sync.ts | 4 +- 10 files changed, 79 insertions(+), 31 deletions(-) diff --git a/.github/workflows/production.yaml b/.github/workflows/production.yaml index c51ef0020..f370f21da 100644 --- a/.github/workflows/production.yaml +++ b/.github/workflows/production.yaml @@ -47,6 +47,9 @@ jobs: GITBOOK_ENDPOINT: https://api.gitbook.com GITBOOK_ORGANIZATION: gitbook OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + # Proxy + PROXY_URL: ${{ secrets.PROXY_URL }} + PROXY_SECRET: ${{ secrets.PROXY_SECRET }} # GitHub Files UNFURL_GITHUB_CLIENT_ID: ${{ secrets.UNFURL_GITHUB_CLIENT_ID }} UNFURL_GITHUB_CLIENT_SECRET: ${{ secrets.UNFURL_GITHUB_CLIENT_SECRET }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 04ba4d5f9..616465119 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -65,6 +65,9 @@ jobs: GITBOOK_ENDPOINT: https://api.gitbook-staging.com GITBOOK_ORGANIZATION: gitbookio OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + # Proxy + PROXY_URL: ${{ secrets.PROXY_URL }} + PROXY_SECRET: ${{ secrets.PROXY_SECRET }} # GitHub Files UNFURL_GITHUB_CLIENT_ID: ${{ secrets.UNFURL_GITHUB_CLIENT_ID }} UNFURL_GITHUB_CLIENT_SECRET: ${{ secrets.UNFURL_GITHUB_CLIENT_SECRET }} diff --git a/bun.lock b/bun.lock index 1ea2b1fcd..3c7b978c9 100644 --- a/bun.lock +++ b/bun.lock @@ -313,7 +313,7 @@ }, "integrations/intercom-conversations": { "name": "@gitbook/integration-intercom-conversations", - "version": "0.1.0", + "version": "0.2.0", "dependencies": { "@gitbook/api": "*", "@gitbook/runtime": "*", @@ -726,7 +726,7 @@ }, "packages/api": { "name": "@gitbook/api", - "version": "0.145.0", + "version": "0.146.0", "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0", @@ -741,7 +741,7 @@ }, "packages/cli": { "name": "@gitbook/cli", - "version": "0.26.0", + "version": "0.26.1", "bin": { "gitbook": "./cli.js", }, diff --git a/integrations/gitlab/gitbook-manifest.yaml b/integrations/gitlab/gitbook-manifest.yaml index ec156b929..34675c615 100644 --- a/integrations/gitlab/gitbook-manifest.yaml +++ b/integrations/gitlab/gitbook-manifest.yaml @@ -41,4 +41,7 @@ scopes: configurations: space: componentId: configure +secrets: + PROXY_URL: ${{ env.PROXY_URL }} + PROXY_SECRET: ${{ env.PROXY_SECRET }} target: space diff --git a/integrations/gitlab/src/api.ts b/integrations/gitlab/src/api.ts index 41b1a8d05..2f62e4678 100644 --- a/integrations/gitlab/src/api.ts +++ b/integrations/gitlab/src/api.ts @@ -2,7 +2,8 @@ import LinkHeader from 'http-link-header'; import { Logger, ExposableError } from '@gitbook/runtime'; -import type { GitLabSpaceConfiguration } from './types'; +import type { GitLabRuntimeContext, GitLabSpaceConfiguration } from './types'; +import { signResponse } from './utils'; const logger = Logger('gitlab:api'); @@ -38,8 +39,8 @@ interface GLFetchOptions { /** * Fetch the current GitLab user. It will use the access token from the environment. */ -export async function getCurrentUser(config: GitLabSpaceConfiguration) { - const user = await gitlabAPI(config, { +export async function getCurrentUser(context: GitLabRuntimeContext, config: GitLabSpaceConfiguration) { + const user = await gitlabAPI(context, config, { path: '/user', }); @@ -51,10 +52,11 @@ export async function getCurrentUser(config: GitLabSpaceConfiguration) { * the access token from the environment. */ export async function fetchProjects( + context: GitLabRuntimeContext, config: GitLabSpaceConfiguration, options: GLFetchOptions = {}, ) { - const projects = await gitlabAPI>(config, { + const projects = await gitlabAPI>(context, config, { path: '/projects', params: { membership: true, @@ -71,11 +73,12 @@ export async function fetchProjects( * Search currently authenticated user projects for a given query. */ export async function searchUserProjects( + context: GitLabRuntimeContext, config: GitLabSpaceConfiguration, search: string, options: GLFetchOptions = {}, ) { - const projects = await gitlabAPI>(config, { + const projects = await gitlabAPI>(context, config, { path: `/users/${config.userId}/projects`, params: { search, @@ -91,8 +94,8 @@ export async function searchUserProjects( /** * Fetch a GitLab project by its ID. */ -export async function fetchProject(config: GitLabSpaceConfiguration, projectId: number) { - const project = await gitlabAPI(config, { +export async function fetchProject(context: GitLabRuntimeContext, config: GitLabSpaceConfiguration, projectId: number) { + const project = await gitlabAPI(context, config, { path: `/projects/${projectId}`, }); @@ -102,8 +105,8 @@ export async function fetchProject(config: GitLabSpaceConfiguration, projectId: /** * Fetch all branches for a given project repository. */ -export async function fetchProjectBranches(config: GitLabSpaceConfiguration, projectId: number) { - const branches = await gitlabAPI>(config, { +export async function fetchProjectBranches(context: GitLabRuntimeContext, config: GitLabSpaceConfiguration, projectId: number) { + const branches = await gitlabAPI>(context, config, { path: `/projects/${projectId}/repository/branches`, params: { per_page: 100, @@ -118,12 +121,13 @@ export async function fetchProjectBranches(config: GitLabSpaceConfiguration, pro * Configure a GitLab webhook for a given project. */ export async function addProjectWebhook( + context: GitLabRuntimeContext, config: GitLabSpaceConfiguration, projectId: number, webhookUrl: string, webhookToken: string, ) { - const { id } = await gitlabAPI<{ id: number }>(config, { + const { id } = await gitlabAPI<{ id: number }>(context, config, { method: 'POST', path: `/projects/${projectId}/hooks`, body: { @@ -141,11 +145,12 @@ export async function addProjectWebhook( * Delete a GitLab webhook for a given project. */ export async function deleteProjectWebhook( + context: GitLabRuntimeContext, config: GitLabSpaceConfiguration, projectId: number, webhookId: number, ) { - await gitlabAPI(config, { + await gitlabAPI(context, config, { method: 'DELETE', path: `/projects/${projectId}/hooks/${webhookId}`, }); @@ -155,12 +160,13 @@ export async function deleteProjectWebhook( * Create a commit status for a commit SHA. */ export async function editCommitStatus( + context: GitLabRuntimeContext, config: GitLabSpaceConfiguration, projectId: number, sha: string, status: object, ): Promise { - await gitlabAPI(config, { + await gitlabAPI(context, config, { method: 'POST', path: `/projects/${projectId}/statuses/${sha}`, body: status, @@ -171,6 +177,7 @@ export async function editCommitStatus( * Execute a GitLab API request. */ export async function gitlabAPI( + context: GitLabRuntimeContext, config: GitLabSpaceConfiguration, request: { method?: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE'; @@ -209,7 +216,7 @@ export async function gitlabAPI( body: body ? JSON.stringify(body) : undefined, }; - const response = await requestGitLab(token, url, options); + const response = await requestGitLab(context, token, url, options); const isJSONResponse = response.headers.get('Content-Type')?.includes('application/json'); if (!isJSONResponse) { @@ -234,7 +241,7 @@ export async function gitlabAPI( const nextURLSearchParams = Object.fromEntries(nextURL.searchParams); if (nextURLSearchParams.page) { url.searchParams.set('page', nextURLSearchParams.page as string); - const nextResponse = await requestGitLab(token, url, options); + const nextResponse = await requestGitLab(context, token, url, options); const nextData = await nextResponse.json(); // @ts-ignore data = [...data, ...(paginatedListProperty ? nextData[listProperty] : nextData)]; @@ -253,12 +260,24 @@ export async function gitlabAPI( * It will throw an error if the response is not ok. */ async function requestGitLab( + context: GitLabRuntimeContext, token: string, url: URL, options: RequestInit = {}, ): Promise { logger.debug(`GitLab API -> [${options.method}] ${url.toString()}`); - const response = await fetch(url.toString(), { + // Hardcoded test org, will need to switch to use Reflag for that. + const shouldUseProxy = context.environment.installation?.target.organization === "bpM5n3M20vPLEwz3Nsi2"; + const response = shouldUseProxy ? await proxyRequest(context, url.toString(), { + ...options, + headers: { + ...options.headers, + ...(options.body ? { 'Content-Type': 'application/json' } : {}), + Accept: 'application/json', + Authorization: `Bearer ${token}`, + 'User-Agent': 'GitLab-Integration-Worker', + }, + }) : await fetch(url.toString(), { ...options, headers: { ...options.headers, @@ -303,3 +322,19 @@ export function getAccessTokenOrThrow(config: GitLabSpaceConfiguration): string return accessToken; } + + +export async function proxyRequest(context: GitLabRuntimeContext, url: string, options: RequestInit = {}): Promise { + const signature = await signResponse(url, context.environment.secrets.PROXY_SECRET); + const proxyUrl = new URL(context.environment.secrets.PROXY_URL); + + proxyUrl.searchParams.set('target', url); + + return fetch(proxyUrl.toString(), { + ...options, + headers: { + ...options.headers, + 'X-Gitbook-Proxy-Signature': signature, + }, + }); +} \ No newline at end of file diff --git a/integrations/gitlab/src/components.tsx b/integrations/gitlab/src/components.tsx index afafb1598..db33cab82 100644 --- a/integrations/gitlab/src/components.tsx +++ b/integrations/gitlab/src/components.tsx @@ -68,7 +68,7 @@ export const configBlock = createComponent< : undefined, }; - const glUser = await getCurrentUser(config); + const glUser = await getCurrentUser(context, config); await context.api.integrations.updateIntegrationSpaceInstallation( spaceInstallation.integration, @@ -149,7 +149,7 @@ export const configBlock = createComponent< case 'save.configuration': { await saveSpaceConfiguration(context, element.state); - return { type: 'complete' }; + return { type: 'complete' as const }; } } }, diff --git a/integrations/gitlab/src/index.ts b/integrations/gitlab/src/index.ts index a2bb96c81..6f9b92185 100644 --- a/integrations/gitlab/src/index.ts +++ b/integrations/gitlab/src/index.ts @@ -164,6 +164,7 @@ const handleFetchEvent: FetchEventCallback = async (reques if (querySelectedProject) { try { const selectedProject = await fetchProject( + context, spaceConfig, parseInt(querySelectedProject, 10), ); @@ -180,7 +181,7 @@ const handleFetchEvent: FetchEventCallback = async (reques if (queryProject) { const q = encodeURIComponent(queryProject); - const searchedProjects = await searchUserProjects(spaceConfig, q, { + const searchedProjects = await searchUserProjects(context, spaceConfig, q, { page: 1, per_page: 100, walkPagination: false, @@ -201,7 +202,7 @@ const handleFetchEvent: FetchEventCallback = async (reques }); } else { const page = pageNumber || 1; - const projects = await fetchProjects(spaceConfig, { + const projects = await fetchProjects(context, spaceConfig, { page, per_page: 100, walkPagination: false, @@ -251,7 +252,7 @@ const handleFetchEvent: FetchEventCallback = async (reques const querySelectedBranch = selectedBranch && typeof selectedBranch === 'string' ? selectedBranch : undefined; - const branches = projectId ? await fetchProjectBranches(config, projectId) : []; + const branches = projectId ? await fetchProjectBranches(context, config, projectId) : []; const data = branches.map( (branch): ContentKitSelectOption => ({ @@ -405,7 +406,7 @@ const handleSpaceInstallationDeleted: EventCallback< return; } - await uninstallWebhook(configuration); + await uninstallWebhook(context, configuration); }; export default createIntegration({ diff --git a/integrations/gitlab/src/installation.ts b/integrations/gitlab/src/installation.ts index f4b6da388..16c562d19 100644 --- a/integrations/gitlab/src/installation.ts +++ b/integrations/gitlab/src/installation.ts @@ -45,7 +45,7 @@ export async function saveSpaceConfiguration( const externalIds: string[] = []; externalIds.push(computeConfigQueryKey(projectId, state.branch)); - const glProject = await fetchProject(spaceInstallation.configuration, projectId); + const glProject = await fetchProject(context, spaceInstallation.configuration, projectId); const configurationBody: GitLabSpaceConfiguration = { ...spaceInstallation.configuration, @@ -100,6 +100,7 @@ export async function saveSpaceConfiguration( environment.signingSecrets.integration, ); await installWebhook( + context, updatedSpaceInstallation, createGitLabWebhookURL(context), webhookToken, diff --git a/integrations/gitlab/src/provider.ts b/integrations/gitlab/src/provider.ts index 9d898685c..672b7c031 100644 --- a/integrations/gitlab/src/provider.ts +++ b/integrations/gitlab/src/provider.ts @@ -17,6 +17,7 @@ const logger = Logger('gitlab:provider'); * project */ export async function installWebhook( + context: GitLabRuntimeContext, spaceInstallation: IntegrationSpaceInstallation, webhookUrl: string, webhookToken: string, @@ -26,7 +27,7 @@ export async function installWebhook( assertIsDefined(config.project, { label: 'config.project', statusCode: 400 }); const projectId = config.project; - const id = await addProjectWebhook(config, config.project, webhookUrl, webhookToken); + const id = await addProjectWebhook(context, config, config.project, webhookUrl, webhookToken); logger.info(`Webhook ${id} installed on GitLab project ${projectId}`); @@ -37,14 +38,14 @@ export async function installWebhook( * Remove the GitLab webhook for the currently configured space installation * project. */ -export async function uninstallWebhook(config: GitLabSpaceConfiguration) { +export async function uninstallWebhook(context: GitLabRuntimeContext, config: GitLabSpaceConfiguration) { assertIsDefined(config.project, { label: 'config.project', statusCode: 400 }); assertIsDefined(config.webhookId, { label: 'config.webhookId', statusCode: 400 }); const projectId = config.project; const webhookId = config.webhookId; - await deleteProjectWebhook(config, projectId, webhookId); + await deleteProjectWebhook(context, config, projectId, webhookId); logger.info(`Webhook ${config.webhookId} uninstalled from GitLab project ${projectId}`); } @@ -53,6 +54,7 @@ export async function uninstallWebhook(config: GitLabSpaceConfiguration) { * Update the commit status */ export async function updateCommitStatus( + context: GitLabRuntimeContext, config: GitLabSpaceConfiguration, commitSha: string, update: { @@ -66,7 +68,7 @@ export async function updateCommitStatus( const projectId = config.project; - await editCommitStatus(config, projectId, commitSha, { + await editCommitStatus(context, config, projectId, commitSha, { name: update.context || 'GitBook', state: update.state === 'failure' ? 'failed' : update.state, target_url: update.url, diff --git a/integrations/gitlab/src/sync.ts b/integrations/gitlab/src/sync.ts index 26b13d8c4..8a7997b02 100644 --- a/integrations/gitlab/src/sync.ts +++ b/integrations/gitlab/src/sync.ts @@ -179,7 +179,7 @@ export async function updateCommitWithPreviewLinks( const context = `GitBook${config.projectDirectory ? ` (${config.projectDirectory})` : ''}`; - const mainStatus = updateCommitStatus(config, commitSha, { + const mainStatus = updateCommitStatus(runtime, config, commitSha, { state, description: getGitSyncStateDescription(state), url: `${space.urls.app}~/revisions/${revisionId}/`, @@ -190,7 +190,7 @@ export async function updateCommitWithPreviewLinks( if (space.visibility === ContentVisibility.Public) { const publicUrl = space.urls.public; if (publicUrl) { - publicStatus = updateCommitStatus(config, commitSha, { + publicStatus = updateCommitStatus(runtime, config, commitSha, { state, description: getGitSyncStateDescription(state), url: `${publicUrl}~/revisions/${revisionId}/`, From 7cf7b51817c08e409f67d5dd08274a9376f94dee Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Mon, 3 Nov 2025 18:47:08 +0100 Subject: [PATCH 2/7] use reflag --- integrations/gitlab/gitbook-manifest.yaml | 1 + integrations/gitlab/src/api.ts | 31 +++++++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/integrations/gitlab/gitbook-manifest.yaml b/integrations/gitlab/gitbook-manifest.yaml index 34675c615..cb387cc23 100644 --- a/integrations/gitlab/gitbook-manifest.yaml +++ b/integrations/gitlab/gitbook-manifest.yaml @@ -44,4 +44,5 @@ configurations: secrets: PROXY_URL: ${{ env.PROXY_URL }} PROXY_SECRET: ${{ env.PROXY_SECRET }} + REFLAG_SECRET_KEY: ${{ env.REFLAG_SECRET_KEY }} target: space diff --git a/integrations/gitlab/src/api.ts b/integrations/gitlab/src/api.ts index 2f62e4678..d845b4caa 100644 --- a/integrations/gitlab/src/api.ts +++ b/integrations/gitlab/src/api.ts @@ -5,6 +5,7 @@ import { Logger, ExposableError } from '@gitbook/runtime'; import type { GitLabRuntimeContext, GitLabSpaceConfiguration } from './types'; import { signResponse } from './utils'; + const logger = Logger('gitlab:api'); /** @@ -267,8 +268,8 @@ async function requestGitLab( ): Promise { logger.debug(`GitLab API -> [${options.method}] ${url.toString()}`); // Hardcoded test org, will need to switch to use Reflag for that. - const shouldUseProxy = context.environment.installation?.target.organization === "bpM5n3M20vPLEwz3Nsi2"; - const response = shouldUseProxy ? await proxyRequest(context, url.toString(), { + const useProxy = await shouldUseProxy(context); + const response = useProxy ? await proxyRequest(context, url.toString(), { ...options, headers: { ...options.headers, @@ -337,4 +338,30 @@ export async function proxyRequest(context: GitLabRuntimeContext, url: string, o 'X-Gitbook-Proxy-Signature': signature, }, }); +} + +export async function shouldUseProxy(context: GitLabRuntimeContext): Promise { + + const companyId = context.environment.installation?.target.organization; + if(!companyId) { + return false; + } + try { + const response = await fetch(`https://front.reflag.com/features/enabled?context.company.id=${companyId}&key=GIT_SYNC_STATIC_IP`, { + method: "GET", + headers: { + "Authorization": `Bearer ${context.environment.secrets.REFLAG_SECRET_KEY}`, + "Content-Type": "application/json" + } + }); + + const json = await response.json() as {features: {GIT_SYNC_STATIC_IP: {isEnabled: boolean}}}; + const flag = json.features.GIT_SYNC_STATIC_IP; + + return flag.isEnabled; + + } catch(e) { + logger.error('Error checking Reflag feature flag:', e); + return false; + } } \ No newline at end of file From 4f1828a32969217e6a4d8a109870935d0d1916cc Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Tue, 4 Nov 2025 21:36:34 +0100 Subject: [PATCH 3/7] updated env var --- .github/workflows/production.yaml | 1 + .github/workflows/release.yaml | 5 +++-- .github/workflows/test.yaml | 4 ++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/production.yaml b/.github/workflows/production.yaml index f370f21da..7f5b3b0cc 100644 --- a/.github/workflows/production.yaml +++ b/.github/workflows/production.yaml @@ -50,6 +50,7 @@ jobs: # Proxy PROXY_URL: ${{ secrets.PROXY_URL }} PROXY_SECRET: ${{ secrets.PROXY_SECRET }} + REFLAG_SECRET_KEY: ${{ secrets.REFLAG_SECRET_KEY }} # GitHub Files UNFURL_GITHUB_CLIENT_ID: ${{ secrets.UNFURL_GITHUB_CLIENT_ID }} UNFURL_GITHUB_CLIENT_SECRET: ${{ secrets.UNFURL_GITHUB_CLIENT_SECRET }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 616465119..575c5cdf5 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -66,8 +66,9 @@ jobs: GITBOOK_ORGANIZATION: gitbookio OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} # Proxy - PROXY_URL: ${{ secrets.PROXY_URL }} - PROXY_SECRET: ${{ secrets.PROXY_SECRET }} + PROXY_URL: ${{ secrets.PROXY_STAGING_URL }} + PROXY_SECRET: ${{ secrets.PROXY_STAGING_SECRET }} + REFLAG_SECRET_KEY: ${{ secrets.REFLAG_STAGING_SECRET_KEY }} # GitHub Files UNFURL_GITHUB_CLIENT_ID: ${{ secrets.UNFURL_GITHUB_CLIENT_ID }} UNFURL_GITHUB_CLIENT_SECRET: ${{ secrets.UNFURL_GITHUB_CLIENT_SECRET }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index aa5631004..f71d5687e 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -81,6 +81,10 @@ jobs: run: bun run check env: OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + # Proxy + PROXY_URL: ${{ secrets.PROXY_STAGING_URL }} + PROXY_SECRET: ${{ secrets.PROXY_STAGING_SECRET }} + REFLAG_SECRET_KEY: ${{ secrets.REFLAG_STAGING_SECRET_KEY }} # GitHub Files UNFURL_GITHUB_CLIENT_ID: ${{ secrets.UNFURL_GITHUB_CLIENT_ID }} UNFURL_GITHUB_CLIENT_SECRET: ${{ secrets.UNFURL_GITHUB_CLIENT_SECRET }} From 21c592d7e0e0db6226557db7afc9eb0b8c877e92 Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Tue, 4 Nov 2025 21:36:44 +0100 Subject: [PATCH 4/7] linting --- integrations/gitlab/src/api.ts | 94 +++++++++++++++++------------ integrations/gitlab/src/provider.ts | 5 +- integrations/gitlab/src/utils.ts | 5 +- 3 files changed, 64 insertions(+), 40 deletions(-) diff --git a/integrations/gitlab/src/api.ts b/integrations/gitlab/src/api.ts index d845b4caa..a06369e68 100644 --- a/integrations/gitlab/src/api.ts +++ b/integrations/gitlab/src/api.ts @@ -5,7 +5,6 @@ import { Logger, ExposableError } from '@gitbook/runtime'; import type { GitLabRuntimeContext, GitLabSpaceConfiguration } from './types'; import { signResponse } from './utils'; - const logger = Logger('gitlab:api'); /** @@ -40,7 +39,10 @@ interface GLFetchOptions { /** * Fetch the current GitLab user. It will use the access token from the environment. */ -export async function getCurrentUser(context: GitLabRuntimeContext, config: GitLabSpaceConfiguration) { +export async function getCurrentUser( + context: GitLabRuntimeContext, + config: GitLabSpaceConfiguration, +) { const user = await gitlabAPI(context, config, { path: '/user', }); @@ -95,7 +97,11 @@ export async function searchUserProjects( /** * Fetch a GitLab project by its ID. */ -export async function fetchProject(context: GitLabRuntimeContext, config: GitLabSpaceConfiguration, projectId: number) { +export async function fetchProject( + context: GitLabRuntimeContext, + config: GitLabSpaceConfiguration, + projectId: number, +) { const project = await gitlabAPI(context, config, { path: `/projects/${projectId}`, }); @@ -106,7 +112,11 @@ export async function fetchProject(context: GitLabRuntimeContext, config: GitLab /** * Fetch all branches for a given project repository. */ -export async function fetchProjectBranches(context: GitLabRuntimeContext, config: GitLabSpaceConfiguration, projectId: number) { +export async function fetchProjectBranches( + context: GitLabRuntimeContext, + config: GitLabSpaceConfiguration, + projectId: number, +) { const branches = await gitlabAPI>(context, config, { path: `/projects/${projectId}/repository/branches`, params: { @@ -269,25 +279,27 @@ async function requestGitLab( logger.debug(`GitLab API -> [${options.method}] ${url.toString()}`); // Hardcoded test org, will need to switch to use Reflag for that. const useProxy = await shouldUseProxy(context); - const response = useProxy ? await proxyRequest(context, url.toString(), { - ...options, - headers: { - ...options.headers, - ...(options.body ? { 'Content-Type': 'application/json' } : {}), - Accept: 'application/json', - Authorization: `Bearer ${token}`, - 'User-Agent': 'GitLab-Integration-Worker', - }, - }) : await fetch(url.toString(), { - ...options, - headers: { - ...options.headers, - ...(options.body ? { 'Content-Type': 'application/json' } : {}), - Accept: 'application/json', - Authorization: `Bearer ${token}`, - 'User-Agent': 'GitLab-Integration-Worker', - }, - }); + const response = useProxy + ? await proxyRequest(context, url.toString(), { + ...options, + headers: { + ...options.headers, + ...(options.body ? { 'Content-Type': 'application/json' } : {}), + Accept: 'application/json', + Authorization: `Bearer ${token}`, + 'User-Agent': 'GitLab-Integration-Worker', + }, + }) + : await fetch(url.toString(), { + ...options, + headers: { + ...options.headers, + ...(options.body ? { 'Content-Type': 'application/json' } : {}), + Accept: 'application/json', + Authorization: `Bearer ${token}`, + 'User-Agent': 'GitLab-Integration-Worker', + }, + }); if (!response.ok) { const text = await response.text(); @@ -324,8 +336,11 @@ export function getAccessTokenOrThrow(config: GitLabSpaceConfiguration): string return accessToken; } - -export async function proxyRequest(context: GitLabRuntimeContext, url: string, options: RequestInit = {}): Promise { +export async function proxyRequest( + context: GitLabRuntimeContext, + url: string, + options: RequestInit = {}, +): Promise { const signature = await signResponse(url, context.environment.secrets.PROXY_SECRET); const proxyUrl = new URL(context.environment.secrets.PROXY_URL); @@ -341,27 +356,30 @@ export async function proxyRequest(context: GitLabRuntimeContext, url: string, o } export async function shouldUseProxy(context: GitLabRuntimeContext): Promise { - const companyId = context.environment.installation?.target.organization; - if(!companyId) { + if (!companyId) { return false; } try { - const response = await fetch(`https://front.reflag.com/features/enabled?context.company.id=${companyId}&key=GIT_SYNC_STATIC_IP`, { - method: "GET", - headers: { - "Authorization": `Bearer ${context.environment.secrets.REFLAG_SECRET_KEY}`, - "Content-Type": "application/json" - } - }); + const response = await fetch( + `https://front.reflag.com/features/enabled?context.company.id=${companyId}&key=GIT_SYNC_STATIC_IP`, + { + method: 'GET', + headers: { + Authorization: `Bearer ${context.environment.secrets.REFLAG_SECRET_KEY}`, + 'Content-Type': 'application/json', + }, + }, + ); - const json = await response.json() as {features: {GIT_SYNC_STATIC_IP: {isEnabled: boolean}}}; + const json = (await response.json()) as { + features: { GIT_SYNC_STATIC_IP: { isEnabled: boolean } }; + }; const flag = json.features.GIT_SYNC_STATIC_IP; return flag.isEnabled; - - } catch(e) { + } catch (e) { logger.error('Error checking Reflag feature flag:', e); return false; } -} \ No newline at end of file +} diff --git a/integrations/gitlab/src/provider.ts b/integrations/gitlab/src/provider.ts index 672b7c031..eebf84413 100644 --- a/integrations/gitlab/src/provider.ts +++ b/integrations/gitlab/src/provider.ts @@ -38,7 +38,10 @@ export async function installWebhook( * Remove the GitLab webhook for the currently configured space installation * project. */ -export async function uninstallWebhook(context: GitLabRuntimeContext, config: GitLabSpaceConfiguration) { +export async function uninstallWebhook( + context: GitLabRuntimeContext, + config: GitLabSpaceConfiguration, +) { assertIsDefined(config.project, { label: 'config.project', statusCode: 400 }); assertIsDefined(config.webhookId, { label: 'config.webhookId', statusCode: 400 }); diff --git a/integrations/gitlab/src/utils.ts b/integrations/gitlab/src/utils.ts index c895753a8..85f58febf 100644 --- a/integrations/gitlab/src/utils.ts +++ b/integrations/gitlab/src/utils.ts @@ -73,7 +73,10 @@ export function computeConfigQueryKey(projectId: number, ref: string): string { export function normalizeInstanceUrl(url: string): string { let instanceUrl = url.trim(); instanceUrl = instanceUrl.endsWith('/') ? instanceUrl.slice(0, -1) : instanceUrl; - instanceUrl = instanceUrl.startsWith('https://') ? instanceUrl : `https://${instanceUrl}`; + instanceUrl = + instanceUrl.startsWith('https://') || instanceUrl.startsWith('http://') + ? instanceUrl + : `https://${instanceUrl}`; return instanceUrl; } From 18c8c5dc51884181d1398c40413557fe991efbe6 Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Tue, 4 Nov 2025 21:37:50 +0100 Subject: [PATCH 5/7] changeset --- .changeset/young-carrots-mix.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/young-carrots-mix.md diff --git a/.changeset/young-carrots-mix.md b/.changeset/young-carrots-mix.md new file mode 100644 index 000000000..5f13e6b8d --- /dev/null +++ b/.changeset/young-carrots-mix.md @@ -0,0 +1,5 @@ +--- +'@gitbook/integration-gitlab': minor +--- + +Add support for custom proxy From 21980ce0af8cf8173a72b8d24c147226e5e7374a Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Wed, 5 Nov 2025 16:04:51 +0100 Subject: [PATCH 6/7] try changing var name --- .github/workflows/production.yaml | 4 ++-- .github/workflows/release.yaml | 4 ++-- .github/workflows/test.yaml | 4 ++-- bun.lock | 4 ++-- integrations/gitlab/gitbook-manifest.yaml | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/production.yaml b/.github/workflows/production.yaml index 7f5b3b0cc..e23d80442 100644 --- a/.github/workflows/production.yaml +++ b/.github/workflows/production.yaml @@ -48,8 +48,8 @@ jobs: GITBOOK_ORGANIZATION: gitbook OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} # Proxy - PROXY_URL: ${{ secrets.PROXY_URL }} - PROXY_SECRET: ${{ secrets.PROXY_SECRET }} + GITBOOK_PROXY_URL: ${{ secrets.PROXY_URL }} + GITBOOK_PROXY_SECRET: ${{ secrets.PROXY_SECRET }} REFLAG_SECRET_KEY: ${{ secrets.REFLAG_SECRET_KEY }} # GitHub Files UNFURL_GITHUB_CLIENT_ID: ${{ secrets.UNFURL_GITHUB_CLIENT_ID }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 575c5cdf5..cdbbc98d5 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -66,8 +66,8 @@ jobs: GITBOOK_ORGANIZATION: gitbookio OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} # Proxy - PROXY_URL: ${{ secrets.PROXY_STAGING_URL }} - PROXY_SECRET: ${{ secrets.PROXY_STAGING_SECRET }} + GITBOOK_PROXY_URL: ${{ secrets.PROXY_STAGING_URL }} + GITBOOK_PROXY_SECRET: ${{ secrets.PROXY_STAGING_SECRET }} REFLAG_SECRET_KEY: ${{ secrets.REFLAG_STAGING_SECRET_KEY }} # GitHub Files UNFURL_GITHUB_CLIENT_ID: ${{ secrets.UNFURL_GITHUB_CLIENT_ID }} diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index f71d5687e..8ed06f877 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -82,8 +82,8 @@ jobs: env: OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} # Proxy - PROXY_URL: ${{ secrets.PROXY_STAGING_URL }} - PROXY_SECRET: ${{ secrets.PROXY_STAGING_SECRET }} + GITBOOK_PROXY_URL: ${{ secrets.PROXY_STAGING_URL }} + GITBOOK_PROXY_SECRET: ${{ secrets.PROXY_STAGING_SECRET }} REFLAG_SECRET_KEY: ${{ secrets.REFLAG_STAGING_SECRET_KEY }} # GitHub Files UNFURL_GITHUB_CLIENT_ID: ${{ secrets.UNFURL_GITHUB_CLIENT_ID }} diff --git a/bun.lock b/bun.lock index 3c7b978c9..36670f6f3 100644 --- a/bun.lock +++ b/bun.lock @@ -726,7 +726,7 @@ }, "packages/api": { "name": "@gitbook/api", - "version": "0.146.0", + "version": "0.147.0", "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0", @@ -741,7 +741,7 @@ }, "packages/cli": { "name": "@gitbook/cli", - "version": "0.26.1", + "version": "0.26.2", "bin": { "gitbook": "./cli.js", }, diff --git a/integrations/gitlab/gitbook-manifest.yaml b/integrations/gitlab/gitbook-manifest.yaml index cb387cc23..d6e7b6e4b 100644 --- a/integrations/gitlab/gitbook-manifest.yaml +++ b/integrations/gitlab/gitbook-manifest.yaml @@ -42,7 +42,7 @@ configurations: space: componentId: configure secrets: - PROXY_URL: ${{ env.PROXY_URL }} - PROXY_SECRET: ${{ env.PROXY_SECRET }} + PROXY_URL: ${{ env.GITBOOK_PROXY_URL }} + PROXY_SECRET: ${{ env.GITBOOK_PROXY_SECRET }} REFLAG_SECRET_KEY: ${{ env.REFLAG_SECRET_KEY }} target: space From 101a28e6899e15992f2224f0a9ec183d6d0ce54d Mon Sep 17 00:00:00 2001 From: Nicolas Dorseuil Date: Wed, 5 Nov 2025 16:44:44 +0100 Subject: [PATCH 7/7] add env in turbo --- turbo.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/turbo.json b/turbo.json index 2da1b0479..4ae758de8 100644 --- a/turbo.json +++ b/turbo.json @@ -42,7 +42,10 @@ "GITHUBCOPILOT_APP_ID", "GITHUBCOPILOT_APP_URL", "GITHUBCOPILOT_CLIENT_ID", - "GITHUBCOPILOT_CLIENT_SECRET" + "GITHUBCOPILOT_CLIENT_SECRET", + "GITBOOK_PROXY_URL", + "GITBOOK_PROXY_SECRET", + "REFLAG_SECRET_KEY" ], "tasks": { "build": {