Skip to content

OAuth Providers — GitHub & GitLab

Connect your GitHub or GitLab account to enable automated deploys from external repos. Once connected, every push to your default branch triggers a Tekton build + rolling release on PaaS Runtime.

Workflow

sequenceDiagram
  participant T as Tenant (browser)
  participant D as Dashboard
  participant CP as Control Plane
  participant DB as Postgres (oauth_states)
  participant P as GitHub/GitLab

  T->>D: Settings → External Git → "Connect GitHub"
  D->>CP: GET /v1/auth/github/connect
  CP->>DB: INSERT oauth_states (token, 5min TTL)
  CP-->>D: {"authorize_url": "https://github.com/login/oauth/authorize?...&state=..."}
  D->>P: window.location = authorize_url
  P->>T: OAuth consent screen
  T->>CP: GET /v1/auth/github/callback?code=…&state=…
  CP->>DB: DELETE oauth_states WHERE state_token = $1 RETURNING (consume)
  CP->>P: POST /login/oauth/access_token (code + client_secret)
  P-->>CP: {"access_token": "gho_…"}
  CP->>DB: INSERT tenant_git_credentials (encrypted_access_token)
  CP-->>T: {"access_token": "…", "provider": "github"}

Configure OAuth applications (admin)

Before tenants can connect, an admin must register OAuth apps with each provider and inject the credentials into the platform.

GitHub

  1. Open https://github.com/settings/applications/new
  2. Set Authorization callback URL to https://runtime.di2amp.com/api/v1/auth/github/callback
  3. Note the Client ID and Client Secret
  4. Inject into the cluster's paas-secrets K8s Secret:
    kubectl create secret generic paas-secrets -n paas-system \
      --from-literal=GITHUB_OAUTH_CLIENT_ID=Iv1.… \
      --from-literal=GITHUB_OAUTH_CLIENT_SECRET=
  5. Restart the control plane to pick up the new env vars.

GitLab

  1. Open https://gitlab.com/-/profile/applications
  2. Redirect URI: https://runtime.di2amp.com/api/v1/auth/gitlab/callback
  3. Scopes: read_user + read_repository
  4. Note the Application ID and Secret
  5. Inject:
    kubectl create secret generic paas-secrets -n paas-system \
      --from-literal=GITLAB_OAUTH_CLIENT_ID= \
      --from-literal=GITLAB_OAUTH_CLIENT_SECRET=

If credentials are missing, /v1/auth/{provider}/connect returns 503 with OAuth provider not configured by admin so the dashboard can show a clear error.

Security

  • CSRF state — one-time token stored in the oauth_states table with a 5-minute TTL. The callback handler executes DELETE … RETURNING … so each token is consumed exactly once. Replays return 400 immediately.
  • Minimal scopes — GitHub: user:email repo; GitLab: read_user read_repository. We never ask for admin:repo_hook or write scopes outside what's needed for the deploy flow.
  • Token encryption at resttenant_git_credentials.encrypted_access_token is BYTEA (envelope-encrypted at the application layer). The control plane is the only component with the key material.
  • Refresh tokens — GitLab issues refresh tokens; GitHub user-to-server tokens don't expire. Both are stored when present.

Tables

Table Purpose Migration
oauth_states CSRF state tokens (5min TTL, consumed once) 032_oauth_states.sql
app_git_sources Per-app binding (provider + repo + branch + webhook) 030_app_git_sources.sql
tenant_git_credentials Encrypted access tokens per tenant + provider 031_tenant_git_credentials.sql

API Endpoints

Method Path Description
GET /v1/auth/{provider}/connect Mints CSRF state, returns {authorize_url}
GET /v1/auth/{provider}/callback Validates CSRF state, exchanges code for token
GET /v1/auth/oauth/{provider}/authorize (legacy) 302 redirect — same auth URL
GET /v1/auth/oauth/{provider}/callback (legacy) callback without state validation

provider is one of: github, gitlab, forgejo.

Connect, inspect and remove a git source via API

Once OAuth credentials are stored in tenant_git_credentials, link a specific repo to an existing app. The control plane registers a webhook on the provider so future git push events trigger a deploy automatically.

The three endpoints below all share the path /v1/apps/$APP_ID/git_source and require a JWT ($JWT) in the Authorization header.

Connect repo via API

curl -X POST "https://runtime.di2amp.com/api/v1/apps/$APP_ID/git_source" \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "provider":"github",
    "repo_full_name":"octave/test-repo",
    "branch":"main",
    "auto_deploy":true
  }'

Response (201 Created):

{
  "data": {
    "provider": "github",
    "repo_full_name": "octave/test-repo",
    "branch": "main",
    "webhook_id": "12345678"
  }
}
Status Meaning
201 Created Source linked + webhook registered (or warning logged if provider API failed — push deploy still works if you create the webhook manually)
400 bad_request provider not in
404 not_found app_id doesn't exist for this tenant
412 precondition_failed No OAuth credentials for that provider — connect via /v1/auth/{provider}/connect first
422 validation Required field missing in request body

The webhook callback URL is https://runtime.di2amp.com/api/v1/webhooks/{provider}/{app_id} and the webhook secret is a 32-char hex string generated at link time (stored in app_git_sources.webhook_secret).

List current source via API

curl "https://runtime.di2amp.com/api/v1/apps/$APP_ID/git_source" \
  -H "Authorization: Bearer $JWT"

Returns 200 with the current app_git_sources row, or 404 if no source configured.

Disconnect via API

curl -X DELETE "https://runtime.di2amp.com/api/v1/apps/$APP_ID/git_source" \
  -H "Authorization: Bearer $JWT"

Removes the row from app_git_sources and best-effort deletes the webhook on the provider side (GitHub DELETE /repos/{repo}/hooks/{id}, GitLab DELETE /api/v4/projects/{id}/hooks/{id}). Returns 200 with {"data":{"deleted":true,"webhook_deleted":true|false}} or 200 {"deleted":false} if no source was configured.

Disconnect

Tenants can revoke a connected provider any time:

curl -X DELETE -H "Authorization: Bearer $TOKEN" \
  https://runtime.di2amp.com/api/v1/tenant/git-credentials/github

This DELETEs the row from tenant_git_credentials (the upstream OAuth app revocation must be done on the provider side — GitHub: https://github.com/settings/applications, GitLab: profile → Applications).