Features
Git Integration
Git Connections

Git Connections

Git Connections enable secure integration with GitHub and GitLab, allowing OEC.SH to access private repositories for deploying Odoo projects. The platform supports multiple authentication methods to fit your security requirements and workflow.

Git Connections are available at two levels: Platform-level (managed by portal admins) and Organization-level (managed by organization admins).

Overview

Git Connections provide seamless integration with Git providers to:

  • Clone private repositories for Odoo deployments
  • Access custom addon repositories
  • Pull updates during environment synchronization
  • Support multi-provider workflows (GitHub + GitLab)
  • Track connection usage and audit trail

Connection Levels

LevelManaged ByUse CaseSharing
PlatformPortal AdminsGlobal platform repositories (e.g., base Odoo addons)Can be shared with all organizations
OrganizationOrg Admins/OwnersOrganization-specific repositoriesScoped to single organization

Authentication Methods

MethodProviderBest ForToken Refresh
OAuthGitHub, GitLabSimplest setup, automatic token refreshYes (automatic)
Personal Access Token (PAT)GitHub, GitLabLong-term access, CI/CD pipelinesNo (manual rotation)
GitHub AppGitHub onlyAdvanced integration, fine-grained permissionsYes (automatic)
GitLab TokenGitLab onlyProject/Group tokens with specific scopesNo (manual rotation)

GitHub OAuth Connection

OAuth provides the simplest and most secure method to connect GitHub accounts, with automatic token refresh.

Prerequisites

  • GitHub account with access to target repositories
  • Organization membership in OEC.SH (for org-level connections)
  • Portal admin privileges (for platform-level connections)

Connection Flow

Creating GitHub OAuth Connection

Via Web Interface

  1. Navigate to Settings → Git Connections
  2. Click "Add Git Connection"
  3. Select Provider: GitHub
  4. Select Auth Method: OAuth
  5. Click "Connect with GitHub"
  6. Authorize OEC.SH in GitHub (grants: repo, read:org, read:user)
  7. Connection created automatically with your GitHub username

Via API

Step 1: Initiate OAuth Flow

POST /api/v1/git-oauth/authorize
Content-Type: application/json
Authorization: Bearer YOUR_JWT_TOKEN
 
{
  "provider": "github",
  "level": "organization",
  "organization_id": "550e8400-e29b-41d4-a716-446655440000",
  "redirect_url": "https://app.oec.sh/settings/git-connections"
}

Response:

{
  "authorize_url": "https://github.com/login/oauth/authorize?client_id=...",
  "state": "eyJwcm92aWRlciI6ImdpdGh1YiIsImxldmVsIjoib3JnYW5pemF0aW9uIi..."
}

Step 2: User Authorizes on GitHub

Redirect the user to authorize_url. GitHub will redirect back to the configured callback URL with:

https://api.oec.sh/api/v1/auth/github/callback?code=AUTHORIZATION_CODE&state=STATE_PARAM

Step 3: Backend Handles Callback

The backend automatically:

  • Validates the state parameter (CSRF protection)
  • Exchanges authorization code for access/refresh tokens
  • Fetches GitHub user information
  • Creates the Git connection with encrypted tokens
  • Redirects to redirect_url with success/error message

OAuth Scopes

GitHub OAuth connections request the following scopes:

ScopePurpose
repoAccess private repositories (clone, pull)
read:orgRead organization membership (for permission validation)
read:userRead user profile information

Token Expiration

  • GitHub OAuth tokens: Do not expire by default (GitHub classic)
  • GitHub OAuth with expiration: Automatically refreshed before expiration
  • Refresh buffer: Tokens refresh 10 minutes before expiration
⚠️

If a GitHub token is revoked by the user, the connection will enter error status. Re-authorize the connection to restore functionality.


GitLab OAuth Connection

GitLab OAuth provides similar functionality to GitHub OAuth, with automatic token refresh (GitLab tokens expire after 2 hours by default).

Prerequisites

  • GitLab account (GitLab.com or self-hosted instance)
  • Access to target repositories/groups
  • Organization membership in OEC.SH

Connection Flow

The GitLab OAuth flow is identical to GitHub, with provider-specific authorization:

  1. Click "Connect GitLab"
  2. Authorize OEC.SH in GitLab (grants: api, read_user)
  3. Tokens automatically refresh every 2 hours

Creating GitLab OAuth Connection

Via Web Interface

  1. Navigate to Settings → Git Connections
  2. Click "Add Git Connection"
  3. Select Provider: GitLab
  4. Select Auth Method: OAuth
  5. (Optional) Enter GitLab URL for self-hosted instances
  6. Click "Connect with GitLab"
  7. Authorize OEC.SH in GitLab
  8. Connection created with automatic token refresh

Via API

POST /api/v1/git-oauth/authorize
Content-Type: application/json
 
{
  "provider": "gitlab",
  "level": "organization",
  "organization_id": "550e8400-e29b-41d4-a716-446655440000",
  "gitlab_url": "https://gitlab.example.com",  // Optional for self-hosted
  "redirect_url": "https://app.oec.sh/settings/git-connections"
}

Self-Hosted GitLab

OEC.SH supports self-hosted GitLab instances:

  1. Ensure your GitLab instance is accessible from OEC.SH servers
  2. Configure OAuth application in GitLab (Admin → Applications)
  3. Set Redirect URI: https://api.oec.sh/api/v1/auth/gitlab/callback
  4. Note the Application ID and Secret
  5. Provide GitLab URL during connection creation

OAuth Scopes

ScopePurpose
apiFull API access (includes repository access)
read_userRead user profile information

GitLab tokens expire after 2 hours by default. OEC.SH automatically refreshes tokens using the refresh token 10 minutes before expiration.


Personal Access Token (PAT)

Personal Access Tokens provide a manual authentication method suitable for long-term access and CI/CD pipelines.

When to Use PAT

  • Machine-to-machine authentication: CI/CD, automation scripts
  • Long-term access: Tokens don't expire (unless configured)
  • Specific scope requirements: Fine-grained control over permissions
  • OAuth not available: Self-hosted instances without OAuth

Security Considerations

⚠️

Security Best Practices for PATs:

  1. Use minimum required scopes
  2. Rotate tokens regularly (every 90 days recommended)
  3. Never commit tokens to version control
  4. Use separate tokens for different purposes
  5. Revoke tokens immediately if compromised
  6. Enable token expiration when possible

GitHub Personal Access Token

Creating GitHub PAT

  1. Go to GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic)
  2. Click "Generate new token (classic)"
  3. Set Note: OEC.SH Repository Access
  4. Set Expiration: 90 days (recommended)
  5. Select scopes:
    • repo (Full control of private repositories)
    • read:org (Read org membership)
  6. Click "Generate token"
  7. Copy the token (starts with ghp_)

Adding GitHub PAT to OEC.SH

Via Web Interface:

  1. Navigate to Settings → Git Connections
  2. Click "Add Git Connection"
  3. Fill in:
    • Name: GitHub Production Access
    • Provider: GitHub
    • Auth Method: Personal Access Token
    • Token: ghp_EXAMPLE1234567890abcdefghijklmnopqrstuvwxyz (your actual token)
  4. Click "Create Connection"
  5. Connection automatically validated

Via API:

POST /api/v1/organizations/{org_id}/git-connections
Content-Type: application/json
Authorization: Bearer YOUR_JWT_TOKEN
 
{
  "name": "GitHub Production Access",
  "description": "Main GitHub PAT for private repositories",
  "provider": "github",
  "auth_method": "pat",
  "api_token": "ghp_EXAMPLE1234567890abcdefghijklmnopqrstuvwxyz"
}

Response:

{
  "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "organization_id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "GitHub Production Access",
  "provider": "github",
  "auth_method": "pat",
  "status": "active",
  "is_active": true,
  "validated_at": "2025-12-11T10:30:00Z",
  "created_by": "user-uuid-here",
  "create_date": "2025-12-11T10:30:00Z"
}

GitLab Personal Access Token

Creating GitLab PAT

  1. Go to GitLab → Preferences → Access Tokens
  2. Fill in:
    • Token name: OEC.SH Repository Access
    • Expiration date: 90 days from now
    • Select scopes:
      • read_api (Read API)
      • read_repository (Pull repositories)
  3. Click "Create personal access token"
  4. Copy the token (starts with glpat-)

Adding GitLab PAT to OEC.SH

POST /api/v1/organizations/{org_id}/git-connections
Content-Type: application/json
 
{
  "name": "GitLab Private Repos",
  "provider": "gitlab",
  "auth_method": "gitlab_token",
  "api_token": "glpat-EXAMPLE1234567890abcdefghijk",
  "gitlab_url": "https://gitlab.com"  // Or self-hosted URL
}

Token Rotation

PATs do not automatically refresh. Set up token rotation:

  1. Create new token 7 days before expiration
  2. Update connection with new token:
PATCH /api/v1/organizations/{org_id}/git-connections/{connection_id}
Content-Type: application/json
 
{
  "api_token": "ghp_NEW_TOKEN_HERE"
}
  1. Revoke old token after validation
  2. Monitor connection status via /validate endpoint

GitHub App Integration

GitHub Apps provide the most advanced and secure integration method, with fine-grained permissions and automatic token generation.

Advantages of GitHub Apps

  • Fine-grained permissions: Access specific repositories, not all repos
  • Installation-level tokens: Automatically refreshed JWT tokens
  • Audit trail: All actions logged to GitHub's audit log
  • Webhook support: Real-time event notifications
  • No user impersonation: App acts as itself, not a user

Creating a GitHub App

  1. Go to GitHub → Settings → Developer settings → GitHub Apps → New GitHub App
  2. Fill in:
    • GitHub App name: OEC.SH Deployment Bot
    • Homepage URL: https://oec.sh
    • Webhook URL: https://api.oec.sh/api/v1/webhooks/github (optional)
    • Webhook secret: Generate random secret (optional)
  3. Set Repository permissions:
    • Contents: Read-only (to clone/pull)
    • Metadata: Read-only (required)
  4. Set Where can this GitHub App be installed?: Any account
  5. Click "Create GitHub App"
  6. Note the App ID
  7. Generate and download Private Key (PEM format)
  8. Click "Install App" → Select target organization/repositories

Configuring GitHub App in OEC.SH

Via Web Interface:

  1. Navigate to Settings → Git Connections
  2. Click "Add Git Connection"
  3. Fill in:
    • Name: GitHub App - Production
    • Provider: GitHub
    • Auth Method: GitHub App
    • App ID: 123456 (from GitHub App settings)
    • Installation ID: 78901234 (from installation URL)
    • Private Key: Paste entire PEM file content
  4. Click "Create Connection"

Via API:

POST /api/v1/organizations/{org_id}/git-connections
Content-Type: application/json
 
{
  "name": "GitHub App - Production",
  "provider": "github",
  "auth_method": "github_app",
  "github_app_id": "123456",
  "github_app_installation_id": "78901234",
  "github_app_private_key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA...\n-----END RSA PRIVATE KEY-----"
}

Finding Installation ID

The Installation ID is in the URL after installing the app:

https://github.com/settings/installations/78901234
                                            ^^^^^^^^
                                            Installation ID

Or via API:

curl -H "Authorization: Bearer <JWT_TOKEN>" \
     https://api.github.com/app/installations

Token Generation

GitHub Apps use JWT tokens to generate short-lived installation access tokens:

  1. OEC.SH generates JWT using App ID + Private Key
  2. JWT is used to request installation access token
  3. Installation token is valid for 1 hour
  4. Token is automatically refreshed for each deployment

GitHub App tokens are generated on-demand and expire after 1 hour. OEC.SH automatically refreshes tokens as needed.


Repository Selection

Once a Git connection is established, you can browse and select repositories for Odoo deployments.

Listing Repositories

Via API:

GET /api/v1/organizations/{org_id}/git-connections/{connection_id}/repositories?page=1&per_page=30
Authorization: Bearer YOUR_JWT_TOKEN

Response:

{
  "repositories": [
    {
      "id": "123456789",
      "name": "odoo-custom-addons",
      "full_name": "mycompany/odoo-custom-addons",
      "url": "https://github.com/mycompany/odoo-custom-addons",
      "clone_url": "https://github.com/mycompany/odoo-custom-addons.git",
      "private": true,
      "default_branch": "main",
      "description": "Custom Odoo modules for production"
    },
    {
      "id": "987654321",
      "name": "odoo-theme",
      "full_name": "mycompany/odoo-theme",
      "url": "https://github.com/mycompany/odoo-theme",
      "clone_url": "https://github.com/mycompany/odoo-theme.git",
      "private": true,
      "default_branch": "develop",
      "description": "Custom Odoo theme"
    }
  ],
  "page": 1,
  "per_page": 30,
  "total": 2
}

Repository Access Validation

The platform validates repository access before deployment:

  1. Connection status: Must be active
  2. Token validity: OAuth tokens auto-refresh if needed
  3. Repository permissions: Must have read access
  4. Branch existence: Default branch must exist

Using Repositories in Deployments

When creating an environment, specify the Git connection and repository:

POST /api/v1/projects/{project_id}/environments
Content-Type: application/json
 
{
  "name": "Production",
  "odoo_version": "17.0",
  "repository_url": "https://github.com/mycompany/odoo-custom-addons.git",
  "default_branch": "main",
  "git_connection_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
}

Branch Management

Git connections enable browsing and selecting branches for deployment.

Listing Branches

Via API:

GET /api/v1/organizations/{org_id}/git-connections/{connection_id}/branches?repo=mycompany/odoo-custom-addons&page=1&per_page=100
Authorization: Bearer YOUR_JWT_TOKEN

Response:

{
  "branches": [
    {
      "name": "main",
      "is_default": true,
      "is_protected": true
    },
    {
      "name": "develop",
      "is_default": false,
      "is_protected": false
    },
    {
      "name": "feature/new-module",
      "is_default": false,
      "is_protected": false
    }
  ],
  "page": 1,
  "per_page": 100
}

Branch Search

Use the search query parameter to filter branches:

GET /api/v1/organizations/{org_id}/git-connections/{connection_id}/branches?repo=owner/repo&search=feature

Returns only branches matching "feature".

Default Branch

Each repository has a default branch (usually main or master). When creating environments without specifying default_branch, the repository's default branch is used.

Protected Branches

Protected branches (indicated by is_protected: true) have additional safeguards:

  • Cannot be force-pushed
  • Require pull request reviews (if configured in GitHub/GitLab)
  • Recommended for production deployments

Webhook Configuration

Git connections support webhooks for automatic deployment triggers (future feature).

Webhook Setup (Coming Soon)

Webhooks will enable:

  • Auto-deploy on push: Deploy when code is pushed to a branch
  • Pull request previews: Create staging environments for PRs
  • Branch synchronization: Keep deployments in sync with Git

Webhook URLs (Reserved)

GitHub:  https://api.oec.sh/api/v1/webhooks/github/{connection_id}
GitLab:  https://api.oec.sh/api/v1/webhooks/gitlab/{connection_id}

Webhook integration is planned for a future release. Currently, deployments are triggered manually via API or web interface.


Creator Tracking

All Git connections track which user created them, providing an audit trail for security and compliance.

Creator Information

Every Git connection includes created_by field:

{
  "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "name": "GitHub Production",
  "created_by": "550e8400-e29b-41d4-a716-446655440000",
  "create_date": "2025-12-11T10:30:00Z"
}

OAuth Flow Creator Tracking

For OAuth connections, the creator is tracked through the flow:

  1. User initiates OAuth flow (tracked in state parameter)
  2. User authorizes on GitHub/GitLab
  3. Callback creates connection with created_by = initiating_user_id
  4. Connection includes creator information in response

Direct Creation (PAT/GitHub App)

For manually created connections (PAT, GitHub App):

  • created_by is set to the authenticated user making the API call
  • User must have org.git.connections.create permission

Audit Trail

Git connection operations are logged:

EventDescriptionLogged Data
Connection CreatedNew Git connection addedcreator_id, provider, auth_method
Connection UpdatedConnection settings changedupdater_id, changed_fields
Connection ValidatedConnection tested/validatedvalidator_id, validation_result
Connection UsedConnection used for deploymentuser_id, environment_id
Connection DeletedConnection removeddeleter_id, connection_name

Viewing Creator Information

Via Web Interface:

The Git Connections list shows creator name for each connection:

Name                 Provider    Created By      Created At
GitHub Production    GitHub      John Doe        2025-12-11 10:30
GitLab Private       GitLab      Jane Smith      2025-12-09 14:22

Via API:

GET /api/v1/organizations/{org_id}/git-connections

Response includes creator UUID (resolve to user name via Users API).


Connection Management

Updating Connections

Update connection settings (name, description, tokens):

PATCH /api/v1/organizations/{org_id}/git-connections/{connection_id}
Content-Type: application/json
 
{
  "name": "GitHub Production (Updated)",
  "description": "Updated access token on 2025-12-11",
  "api_token": "ghp_NEW_TOKEN_HERE"
}
⚠️

Updating tokens replaces the encrypted stored value. Ensure the new token has the same or greater permissions.

Testing Connections

Validate a connection to ensure it's working:

POST /api/v1/organizations/{org_id}/git-connections/{connection_id}/validate
Authorization: Bearer YOUR_JWT_TOKEN

Response (Success):

{
  "valid": true,
  "username": "mycompany-bot",
  "scopes": ["repo", "read:org"],
  "rate_limit_remaining": 4999
}

Response (Error):

{
  "valid": false,
  "error": "Token expired or revoked",
  "rate_limit_remaining": null
}

Connection Status

Git connections have four status states:

StatusDescriptionAction Required
activeConnection workingNone
expiredOAuth token expiredRe-authorize or refresh token
revokedToken revoked by userRe-create connection
errorValidation failedCheck token, re-create if needed

Removing Connections

Delete a Git connection:

DELETE /api/v1/organizations/{org_id}/git-connections/{connection_id}
Authorization: Bearer YOUR_JWT_TOKEN
🚫

Warning: Deleting a connection will prevent deployments using that connection. Ensure no active environments depend on it before deletion.

Deactivating Connections

Instead of deleting, you can deactivate a connection:

PATCH /api/v1/organizations/{org_id}/git-connections/{connection_id}
Content-Type: application/json
 
{
  "is_active": false
}

Deactivated connections:

  • Remain in the database
  • Cannot be used for new deployments
  • Can be reactivated later

SSH Keys & Deploy Keys

OEC.SH can use SSH deploy keys for additional security with private repositories.

Deploy Keys vs. Git Connections

MethodUse CaseScope
Git ConnectionMulti-repository accessOrganization-wide
Deploy KeySingle repository accessRepository-specific

Adding Deploy Keys (Manual Method)

For repositories that require deploy keys:

  1. Generate SSH key pair on OEC.SH server (handled by platform)
  2. Add public key to repository Settings → Deploy Keys
  3. OEC.SH uses private key to clone/pull

Deploy keys are generated per environment by OEC.SH and injected at deployment time. Git connections provide a more flexible alternative.


Permissions

Git connection operations require specific RBAC permissions:

Organization-Level Permissions

PermissionActionRequired Role
org.git.connections.listView Git connectionsMember, Admin, Owner
org.git.connections.createCreate new connectionsAdmin, Owner
org.git.connections.updateEdit connectionsAdmin, Owner
org.git.connections.deleteRemove connectionsAdmin, Owner
org.git.connections.testValidate connectionsAdmin, Owner

Platform-Level Permissions

Platform Git connections are restricted to portal admins:

  • Only users with portal_admin role can manage platform connections
  • Platform connections can be shared with organizations via allow_org_usage flag

Permission Checks

All API endpoints enforce permissions:

# Example: Creating org connection requires permission
@require_permission("org.git.connections.create")
async def create_org_connection(org_id, data, user):
    # Connection creation logic
    pass

API Reference

Organization Git Connections

List Connections

GET /api/v1/organizations/{organization_id}/git-connections
Authorization: Bearer {token}
 
Query Parameters:
  - active_only: boolean (default: true)

Create Connection

POST /api/v1/organizations/{organization_id}/git-connections
Authorization: Bearer {token}
Content-Type: application/json
 
Body:
{
  "name": string (required, 1-100 chars),
  "description": string | null,
  "provider": "github" | "gitlab",
  "auth_method": "oauth" | "pat" | "github_app" | "gitlab_token",
  "access_token": string | null,  // For OAuth
  "refresh_token": string | null,  // For OAuth
  "api_token": string | null,  // For PAT
  "github_app_id": string | null,
  "github_app_installation_id": string | null,
  "github_app_private_key": string | null,
  "gitlab_url": string | null  // For self-hosted GitLab
}

Get Connection

GET /api/v1/organizations/{organization_id}/git-connections/{connection_id}
Authorization: Bearer {token}

Update Connection

PATCH /api/v1/organizations/{organization_id}/git-connections/{connection_id}
Authorization: Bearer {token}
Content-Type: application/json
 
Body: (all fields optional)
{
  "name": string,
  "description": string,
  "api_token": string,
  "is_active": boolean
}

Delete Connection

DELETE /api/v1/organizations/{organization_id}/git-connections/{connection_id}
Authorization: Bearer {token}

Validate Connection

POST /api/v1/organizations/{organization_id}/git-connections/{connection_id}/validate
Authorization: Bearer {token}
 
Response:
{
  "valid": boolean,
  "username": string | null,
  "scopes": string[] | null,
  "error": string | null,
  "rate_limit_remaining": number | null
}

List Repositories

GET /api/v1/organizations/{organization_id}/git-connections/{connection_id}/repositories
Authorization: Bearer {token}
 
Query Parameters:
  - page: number (default: 1)
  - per_page: number (default: 30, max: 100)

List Branches

GET /api/v1/organizations/{organization_id}/git-connections/{connection_id}/branches
Authorization: Bearer {token}
 
Query Parameters:
  - repo: string (required) - Full repo name (e.g., "owner/repo")
  - search: string | null - Filter branches by name
  - page: number (default: 1)
  - per_page: number (default: 100, max: 100)

Platform Git Connections

Platform endpoints follow the same pattern with /api/v1/platform/git-connections prefix. All operations require portal admin privileges.

OAuth Flow Endpoints

Get OAuth Status

GET /api/v1/git-oauth/status
 
Response:
{
  "github_enabled": boolean,
  "gitlab_enabled": boolean
}

Initiate OAuth Flow

POST /api/v1/git-oauth/authorize
Authorization: Bearer {token}
Content-Type: application/json
 
Body:
{
  "provider": "github" | "gitlab",
  "level": "platform" | "organization",
  "organization_id": string | null,  // Required for org-level
  "redirect_url": string | null,  // Where to redirect after callback
  "gitlab_url": string | null  // For self-hosted GitLab
}
 
Response:
{
  "authorize_url": string,
  "state": string
}

OAuth Callback (Handled Automatically)

GET /api/v1/auth/{provider}/callback?code={code}&state={state}
 
Note: This endpoint is called by GitHub/GitLab after user authorization.
The backend automatically handles token exchange and connection creation.

List Available Connections

GET /api/v1/git-oauth/connections
Authorization: Bearer {token}
 
Response:
{
  "connections": [
    {
      "id": string,
      "provider": string,
      "username": string,
      "name": string,
      "level": "platform" | "organization",
      "organization_id": string | null,
      "organization_name": string | null
    }
  ]
}

Refresh OAuth Token

POST /api/v1/git-oauth/refresh/{connection_id}
Authorization: Bearer {token}
 
Response:
{
  "success": boolean,
  "message": string,
  "expires_at": string | null
}

Troubleshooting

OAuth Flow Errors

"OAuth callback URL mismatch"

Cause: Callback URL in provider settings doesn't match the one used in authorization.

Solution:

  1. Check GitHub/GitLab OAuth app settings
  2. Ensure callback URL is exactly: https://api.oec.sh/api/v1/auth/{provider}/callback
  3. No trailing slash
  4. HTTPS required

"State parameter mismatch"

Cause: CSRF protection detected a tampered state parameter.

Solution:

  1. Ensure the state parameter is not modified during redirect
  2. Complete OAuth flow within 10 minutes (state expires)
  3. Don't refresh or go back during OAuth flow

"Token exchange failed"

Cause: Invalid authorization code or expired code.

Solution:

  1. Authorization codes expire after 10 minutes
  2. Re-initiate OAuth flow from the beginning
  3. Don't reuse authorization codes (single-use only)

Permission Issues

"403 Forbidden: Invalid token"

Cause: PAT doesn't have required scopes or was revoked.

Solution:

  1. Validate connection: POST /validate
  2. Check token scopes match requirements
  3. Generate new token with correct scopes
  4. Update connection with new token

"404 Not Found: Repository not found"

Cause: Token doesn't have access to the repository.

Solution:

  1. For PAT: Ensure token has repo scope
  2. For GitHub App: Check app installation includes the repository
  3. For OAuth: Re-authorize to update repository access

"Rate limit exceeded"

Cause: Too many API requests to GitHub/GitLab.

Solution:

  1. GitHub: 5,000 requests/hour for OAuth, 60/hour for unauthenticated
  2. GitLab: 2,000 requests/hour for authenticated users
  3. Wait for rate limit reset (check X-RateLimit-Reset header)
  4. Use GitHub App (higher rate limits: 15,000/hour)

Connection Validation Failures

"Connection status: expired"

Cause: OAuth token expired and refresh failed.

Solution:

  1. Manually refresh token: POST /git-oauth/refresh/{connection_id}
  2. If refresh fails, re-authorize the connection
  3. For PAT, generate new token and update connection

"Connection status: revoked"

Cause: User revoked token in GitHub/GitLab settings.

Solution:

  1. Delete old connection
  2. Create new connection with fresh authorization
  3. Update environments to use new connection

"Connection status: error"

Cause: Validation failed (network issue, invalid credentials, etc.)

Solution:

  1. Check connection: POST /validate for detailed error
  2. Review error message in validation_error field
  3. Test token manually with curl:
# GitHub
curl -H "Authorization: Bearer YOUR_TOKEN" https://api.github.com/user
 
# GitLab
curl -H "Authorization: Bearer YOUR_TOKEN" https://gitlab.com/api/v4/user

GitLab Self-Hosted Issues

"SSL certificate verification failed"

Cause: Self-hosted GitLab uses self-signed certificate.

Solution:

  1. Ensure GitLab instance has valid SSL certificate
  2. If using self-signed, contact OEC.SH support to add exception
  3. Use HTTP (not recommended for production)

"GitLab OAuth not configured"

Cause: OEC.SH doesn't have OAuth credentials for your GitLab instance.

Solution:

  1. Self-hosted GitLab requires per-instance OAuth configuration
  2. Contact OEC.SH support to add your GitLab instance
  3. Provide OAuth application ID and secret

Token Security

"Token leaked in logs"

Cause: Tokens should never appear in logs.

Solution:

  1. Immediately revoke compromised token in GitHub/GitLab
  2. Generate new token
  3. Update connection with new token
  4. Review audit logs for unauthorized access
  5. Report to security@oec.sh if suspicious activity detected

"Connection used by unauthorized user"

Cause: User gained access to connection without proper permissions.

Solution:

  1. Review organization membership
  2. Check user's role and permissions
  3. Audit connection usage: Review last_used_at and deployment logs
  4. If necessary, deactivate or delete connection

Best Practices

  1. Use OAuth when possible: Automatic refresh, easier management
  2. Rotate PATs regularly: Set expiration dates, rotate every 90 days
  3. Use GitHub Apps for production: Fine-grained permissions, better security
  4. Monitor connection status: Set up alerts for expired or error status
  5. Audit connection usage: Review creator tracking and last usage dates
  6. Limit token scopes: Use minimum required permissions
  7. Use separate connections per environment: Production, staging, development

Security & Encryption

All sensitive data in Git connections is encrypted at rest.

Encryption Method

  • Algorithm: Fernet (symmetric encryption)
  • Key Management: Environment variable FERNET_ENCRYPTION_KEY
  • Encrypted Fields: access_token, refresh_token, api_token, github_app_private_key

Token Storage

# Example encryption (internal)
from cryptography.fernet import Fernet
 
cipher = Fernet(encryption_key)
encrypted_token = cipher.encrypt(plain_token.encode())
 
# Stored in database as encrypted bytes
connection.access_token = encrypted_token

Token Retrieval

Tokens are decrypted on-demand for deployments:

  1. Deployment initiated
  2. Connection fetched from database (encrypted)
  3. Token decrypted in memory
  4. Used for Git operations
  5. Never logged or exposed in API responses

Security Recommendations

⚠️

Critical Security Practices:

  1. Never log tokens: Tokens should never appear in logs, error messages, or API responses
  2. Rotate encryption key: Change FERNET_ENCRYPTION_KEY during upgrades (requires token re-encryption)
  3. Limit token scopes: Use minimum required permissions for each connection
  4. Monitor token usage: Review audit logs for unexpected access patterns
  5. Revoke on compromise: Immediately revoke and recreate connections if token leaked
  6. Use short-lived tokens: Prefer OAuth (auto-refresh) over long-lived PATs
  7. Enable 2FA: Require two-factor authentication for users managing Git connections

Related Documentation


Support

For assistance with Git Connections: