Skip to content

feat: first-class GitLab connect, browse, and push-to-deploy (Phase 1)#177

Open
ThomasRbn wants to merge 18 commits into
oblien:mainfrom
ThomasRbn:feat/gitlab-phase1
Open

feat: first-class GitLab connect, browse, and push-to-deploy (Phase 1)#177
ThomasRbn wants to merge 18 commits into
oblien:mainfrom
ThomasRbn:feat/gitlab-phase1

Conversation

@ThomasRbn

@ThomasRbn ThomasRbn commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Refs #75

Summary

Implements Phase 1 of #75: first-class GitLab as a peer source provider alongside GitHub.

Users can connect GitLab (OAuth or PAT, including self-hosted), browse groups/projects/branches in the Library, bind a project with gitProvider: "gitlab", clone private repos into the existing deploy pipeline, and auto-deploy on Push Hook deliveries.

This does not include Phase 2 (commit statuses, per-server GitLab tokens) or Phase 3 (MR preview environments).

What’s included

Connect

  • Settings → GitLab card: OAuth Application and/or personal access token
  • Self-hosted: per-user GitLab URL on PAT connect (plus instance-wide GITLAB_BASE_URL)
  • Disconnect clears Openship-side OAuth/PAT state without touching GitHub

Library / bind

  • Library provider picker: GitHub | GitLab
  • List namespaces and projects (nested groups supported)
  • Bind sets gitProvider: "gitlab", owner/repo path, branch, clone URL, and numeric GitLab project id (installationId)
  • URL import accepts GitLab HTTPS/SSH URLs

Push-to-deploy

  • Registers a project Push Hook when a reachable webhook URL exists
  • POST /api/webhooks/gitlab with X-Gitlab-Token verification and delivery idempotency
  • Pushes to the tracked branch trigger the existing deploy path; other branches do not

Clone / deploy

  • HTTPS clone via oauth2:<token> (project clone token → user PAT → OAuth)
  • Never falls back to a GitHub credential for gitProvider=gitlab
  • Adapters support custom HTTPS clone usernames (needed for GitLab oauth2)

Docs

  • apps/web/content/docs/guides/deploy-from-gitlab.mdx
  • apps/web/content/docs/api/gitlab.mdx
  • apps/web/content/docs/troubleshooting/gitlab.mdx

Config

GITLAB_CLIENT_ID=
GITLAB_CLIENT_SECRET=
GITLAB_BASE_URL=https://gitlab.com   # or self-hosted origin
GITLAB_WEBHOOK_SECRET=               # optional instance fallback

ThomasRbn added 18 commits July 22, 2026 21:21
Reserve gitlab_webhook_event for delivery idempotency, store GitLab PATs on user_settings, and scope installation/project queries by provider so GitHub and GitLab paths cannot collide.
Allow injectGitToken/assembleGitClone to use oauth2 for GitLab while keeping GitHub's x-access-token default.
Register Better Auth GitLab social login, GITLAB_* config, permission tags, and allow the unified webhook dispatcher to accept gitlab deliveries with provider-scoped GitHub lookups.
Introduce the peer /api/gitlab surface with OAuth/PAT auth, project listing, Push Hook handling, and clone-token resolution without changing the GitHub module.
Link GitLab projects with gitProvider=gitlab, resolve stack via the GitLab reader, and branch build/preflight credential checks so GitLab clones never fall back to GitHub tokens.
Mount GitLabProvider and let users connect via OAuth or PAT from Settings without changing the existing GitHub flow.
Add a Library provider picker, GitLab URL import with nested groups, and provider-aware project source/deploy plumbing that sets gitProvider=gitlab.
Document Phase 1 GitLab deploy flows and note that URL import accepts gitlab.com alongside github.com.
Introduce a new entry for gitlabCloneTokenEncrypted in the secret registry to support GitLab integration.
…nents

- Updated DeployRepository to include provider in dependencies.
- Modified SkeletonLoader and RepositoryList to support GitLab alongside GitHub.
- Added GitLab tab in LibraryPage for project selection.
- Enhanced localization files to include GitLab references in multiple languages.
- Introduced provider-aware URL construction for GitLab and GitHub in project-related services.
- Updated projectGitUrl function to dynamically generate URLs based on the git provider.
- Ensured synchronization of git source fields during project updates to prevent incorrect URLs.
- Added support for GitLab installation IDs in deployment configurations.
- Implemented GitLab-specific webhook registration for auto-deploy, ensuring proper handling of project IDs and webhook URLs.
- Added error handling for missing GitLab tokens and webhook deletion scenarios, providing clearer feedback to users.
- Updated webhook URL construction to accommodate both GitLab and GitHub based on the project's git provider.
Add user_settings.gitlab_base_url so self-hosted instance origins can be saved with a personal access token.
Accept baseUrl on PAT connect, store it with the token, and use it for API calls, clone URL construction, and URL parsing.
Let users enter a self-hosted instance origin next to the personal access token and pass it through connect.
Clarify Settings URL vs instance-wide GITLAB_BASE_URL for self-hosted setups.
build.service imports resolveUserGitlabBaseUrl, which pulls Better Auth
and needs schema from @repo/db. Stub gitlab.auth so the test mock of
repos alone does not blow up on import.
@Hydralerne

Copy link
Copy Markdown
Member

Huge, thank you, will review and merge asap

@Hydralerne

Copy link
Copy Markdown
Member

thank you for the pr, there's some issues before merge i hope to fix before merge

  1. SSRF via the self-hosted base URL — gitlab.http.ts normalizeGitlabBaseUrl allows http:// and any host, applies none of the repo's CLOUD_MODE-gated ssrf-guard; connect fetches the user-supplied baseUrl with a Bearer token before saving. Critical on the SaaS (CLOUD_MODE); low on single-tenant self-hosted (operator points at their own GitLab). Two agents + me.
  2. OAuth "connect" is dead (guaranteed 500) — connectRedirect is a .public() route (no authMiddleware, verified in secure-router.ts:151) yet calls getRequestContext(c), which throws when ctx is absent → every call 500s; the ctx var is then unused. Also drops better-auth's OAuth state cookie (no asResponse/Set-Cookie forwarding). Only PAT connect works. Verified end-to-end.
  3. Cross-org clone-token IDOR — getCloneToken takes a caller-supplied ?projectId → readProjectToken → non-org-scoped findById → returns any project's decrypted long-lived token. Worse: cloneTokenEncrypted is provider-agnostic, so it can even leak a GitHub project's PAT. Constrained by random UUIDs, but a genuine missing authz check (GitHub's version takes no project id and mints owner-scoped). Two agents + me.
  4. Multi-member GitLab deploy is silently blocked — build.service.ts runs assertGitHubRepoAccess on every deploy/redeploy/build-access unconditionally; it's GitHub-grant-only, so a non-owner member (even with the new gitlab grant) hits GITHUB_ACCESS_DENIED 403 on a GitLab project. Fail-closed (over-restriction, not a breach); owner + webhook bypass, so single-owner self-hosted works — but it breaks the multi-user story Phase 1 advertises. Verified.
  5. Missing drizzle snapshots 0057/0058 → next drizzle-kit generate re-emits a duplicate migration.
  6. i18n bypass — new GitLab UI is hardcoded English (no useI18n), unlike GitHub siblings.

@ThomasRbn

Copy link
Copy Markdown
Contributor Author

Great, thanks for your feedback, I'll take a look on this whenever I can 👀

@Hydralerne

Copy link
Copy Markdown
Member

perfect, but anyway really great work

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants