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¶
- Open https://github.com/settings/applications/new
- Set Authorization callback URL to
https://runtime.di2amp.com/api/v1/auth/github/callback - Note the Client ID and Client Secret
- Inject into the cluster's
paas-secretsK8s Secret: - Restart the control plane to pick up the new env vars.
GitLab¶
- Open https://gitlab.com/-/profile/applications
- Redirect URI:
https://runtime.di2amp.com/api/v1/auth/gitlab/callback - Scopes:
read_user+read_repository - Note the Application ID and Secret
- Inject:
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_statestable with a 5-minute TTL. The callback handler executesDELETE … 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 foradmin:repo_hookor write scopes outside what's needed for the deploy flow. - Token encryption at rest —
tenant_git_credentials.encrypted_access_tokenisBYTEA(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¶
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).
Related¶
- Git Push Deploy
- Apps
- Migration drift policy — how
oauth_stateswas added without rewriting earlier migrations