github-bot-sdk 0.2.1

A comprehensive Rust SDK for GitHub App integration with authentication, webhooks, and API client
Documentation
# App-Level Authentication


## Overview


This document clarifies the distinction between app-level and installation-level authentication in the GitHub Bot SDK, addressing use cases where bots need to authenticate as the GitHub App itself rather than as a specific installation.

## Authentication Levels


### App-Level Authentication (JWT)


Authenticate as the GitHub App itself using JWT tokens signed with the app's private key.

**Authorization Header**: `Bearer <JWT>`

**Use Cases**:

1. **Installation Discovery**: List all installations to discover where the app is installed
2. **App Management**: Get app information, update app configuration
3. **Installation Management**: Get installation details, suspend/unsuspend installations
4. **Token Exchange**: Create installation access tokens (the initial exchange)
5. **Marketplace Operations**: Access app marketplace and billing information
6. **Webhook Configuration**: Manage app-level webhook settings

**GitHub API Endpoints**:

- `GET /app` - Get the authenticated app
- `GET /app/installations` - List installations for the authenticated app
- `GET /app/installations/{installation_id}` - Get a specific installation
- `POST /app/installations/{installation_id}/access_tokens` - Create installation token
- `PUT /app/installations/{installation_id}/suspended` - Suspend installation
- `DELETE /app/installations/{installation_id}/suspended` - Unsuspend installation
- `GET /app/hook/config` - Get webhook configuration
- `PATCH /app/hook/config` - Update webhook configuration

**Token Lifetime**: Maximum 10 minutes (GitHub requirement)

**Caching**: Cached and reused until within 2 minutes of expiry (configurable refresh margin). Default JWT lifetime is 10 minutes with 8-minute effective cache duration.

### Installation-Level Authentication (Installation Token)


Authenticate as a specific installation using installation access tokens.

**Authorization Header**: `Bearer <installation_token>` or `token <installation_token>`

**Use Cases**:

1. **Repository Operations**: All operations on repositories (issues, PRs, commits, etc.)
2. **Organization Operations**: Organization-level operations within installation scope
3. **User Operations**: User-specific operations within installation scope
4. **Webhook Processing**: Operations triggered by webhook events
5. **Scheduled Tasks**: Background jobs operating on installed repositories

**GitHub API Endpoints**:

- `GET /repos/{owner}/{repo}/*` - All repository endpoints
- `GET /orgs/{org}/*` - Organization endpoints (within installation scope)
- `GET /user/*` - User endpoints (within installation scope)
- Any operation requiring repository or organization access

**Token Lifetime**: 1 hour from creation (GitHub default)

**Caching**: Should be cached with installation ID as key; refresh before expiration

## Common Bot Patterns


### Pattern 1: Installation Discovery Bot


A bot that needs to discover all installations and then operate on each:

```rust
// 1. Authenticate as app to discover installations
let installations = client.list_installations().await?;

// 2. For each installation, authenticate and operate
for installation in installations {
    let inst_client = client.installation_by_id(installation.id).await?;

    // Now perform installation-scoped operations
    let repos = inst_client.list_repositories().await?;
    for repo in repos {
        process_repository(&inst_client, &repo).await?;
    }
}
```

### Pattern 2: Webhook-Triggered Bot


A bot that responds to webhooks and needs to authenticate for the specific installation:

```rust
// Webhook contains installation_id in payload
let event = parse_webhook(payload)?;

// Authenticate as the specific installation
let inst_client = client.installation_by_id(event.installation.id).await?;

// Perform operations on the repository
inst_client.create_issue_comment(
    &event.repository,
    event.issue.number,
    "Thank you for the issue!"
).await?;
```

### Pattern 3: Repository-Scoped Bot


A bot configured with a specific repository that doesn't need installation discovery:

```rust
// If you know the repository, SDK discovers installation automatically
let repo = Repository {
    owner: "acme".to_string(),
    name: "project".to_string(),
    full_name: "acme/project".to_string(),
};

let inst_client = client.installation(&repo).await?;

// Now perform operations on this repository
let issues = inst_client.list_issues(&Default::default()).await?;
```

### Pattern 4: App Management Bot


A bot that manages the GitHub App itself:

```rust
// Authenticate as app to manage installations
let app = client.get_app().await?;
println!("Managing app: {}", app.name);

// Get specific installation details
let installation = client.get_installation(12345).await?;

// Suspend installation if needed
if installation.suspended_at.is_none() {
    client.post_as_app(
        &format!("/app/installations/{}/suspended", installation.id),
        &json!({})
    ).await?;
}
```

## Implementation Requirements


### AuthenticationProvider Trait


The `AuthenticationProvider` trait must support both authentication levels:

```rust
#[async_trait]

pub trait AuthenticationProvider: Send + Sync {
    /// Get JWT token for app-level GitHub API operations.
    ///
    /// Use this for operations that require authentication as the GitHub App itself, such as:
    /// - Listing installations (`GET /app/installations`)
    /// - Getting app information (`GET /app`)
    /// - Managing installations (`GET /app/installations/{installation_id}`)
    ///
    /// This method handles caching and automatic refresh of JWTs.
    async fn app_token(&self) -> Result<JsonWebToken, AuthError>;

    /// Get installation token for installation-level API operations.
    ///
    /// Use this for operations within a specific installation context, such as:
    /// - Repository operations (reading files, creating issues/PRs)
    /// - Organization operations (team management, webhooks)
    /// - Any operation scoped to the installation's permissions
    ///
    /// This method handles caching and automatic refresh of installation tokens.
    ///
    /// # Arguments
    ///
    /// * `installation_id` - The installation to get a token for
    async fn installation_token(
        &self,
        installation_id: InstallationId,
    ) -> Result<InstallationToken, AuthError>;

    /// Refresh installation token (force new token generation).
    ///
    /// Bypasses cache and requests a new installation token from GitHub.
    /// Use sparingly as it counts against rate limits.
    async fn refresh_installation_token(
        &self,
        installation_id: InstallationId,
    ) -> Result<InstallationToken, AuthError>;

    /// List all installations for this GitHub App.
    ///
    /// Requires app-level authentication. This is a convenience method that combines
    /// app_token() with the list installations API call.
    async fn list_installations(&self) -> Result<Vec<Installation>, AuthError>;

    /// Get repositories accessible by installation.
    ///
    /// Requires installation-level authentication. This is a convenience method that combines
    /// installation_token() with the list repositories API call.
    async fn get_installation_repositories(
        &self,
        installation_id: InstallationId,
    ) -> Result<Vec<Repository>, AuthError>;
}
```

### GitHubClient Interface


The `GitHubClient` must provide methods for both authentication levels:

```rust
impl GitHubClient {
    // App-level operations (use JWT)
    pub async fn list_installations(&self) -> Result<Vec<Installation>, ClientError>;
    pub async fn get_app(&self) -> Result<App, ClientError>;
    pub async fn get_installation(&self, installation_id: u64) -> Result<Installation, ClientError>;
    pub async fn get_as_app(&self, path: &str) -> Result<Response, ClientError>;
    pub async fn post_as_app(&self, path: &str, body: &impl Serialize) -> Result<Response, ClientError>;

    // Installation-level operations (use installation token)
    pub async fn installation(&self, repo: &Repository) -> Result<InstallationClient, ClientError>;
    pub async fn installation_by_id(&self, installation_id: u64) -> Result<InstallationClient, ClientError>;
    pub async fn get(&self, installation_id: u64, path: &str) -> Result<Response, ClientError>;
    pub async fn post(&self, installation_id: u64, path: &str, body: &impl Serialize) -> Result<Response, ClientError>;
}
```

## Security Considerations


### JWT Security


- JWTs authenticate the app itself and have powerful permissions
- Always use HTTPS for API requests
- Never log JWTs or include them in error messages
- Limit JWT expiration to minimum required (max 10 minutes per GitHub)
- Generate fresh JWTs for each app-level operation if possible

### Installation Token Security


- Installation tokens are scoped to specific repositories/organizations
- Use installation tokens for all repository operations (not JWTs)
- Cache installation tokens to avoid excessive token generation
- Refresh tokens before expiration to avoid operation failures
- Tokens are automatically scoped by GitHub to installation permissions

### Principle of Least Privilege


- Use installation tokens whenever possible (more restrictive scope)
- Only use app-level authentication when truly needed
- Configure minimal required permissions for each installation
- Avoid storing tokens longer than necessary

## Testing Strategies


### App-Level Authentication Tests


```rust
#[tokio::test]

async fn test_list_installations() {
    let auth = MockAuthProvider::new()
        .with_jwt("mock-jwt-token");

    let client = GitHubClient::new(auth);

    // Should use JWT authentication
    let installations = client.list_installations().await.unwrap();

    assert!(!installations.is_empty());
}
```

### Installation-Level Authentication Tests


```rust
#[tokio::test]

async fn test_create_issue_with_installation_token() {
    let auth = MockAuthProvider::new()
        .with_installation_token(12345, "mock-installation-token");

    let client = GitHubClient::new(auth);
    let inst_client = client.installation_by_id(12345).await.unwrap();

    // Should use installation token authentication
    let issue = inst_client.create_issue(&repo, &issue_data).await.unwrap();

    assert_eq!(issue.title, "Test Issue");
}
```

### Authentication Context Tests


```rust
#[tokio::test]

async fn test_automatic_authentication_context() {
    let client = setup_client();

    // App-level call uses JWT
    let app = client.get_app().await.unwrap();
    assert_uses_jwt_auth(&app);

    // Installation-level call uses installation token
    let inst_client = client.installation_by_id(12345).await.unwrap();
    let repos = inst_client.list_repositories().await.unwrap();
    assert_uses_installation_token_auth(&repos);
}
```

## Migration Guide


For existing code that only implements installation-level authentication:

1. **Identify app-level operations**: Review your code for operations that might benefit from app-level authentication
2. **Add app-level methods**: Implement `list_installations()`, `get_app()`, and other app-level operations
3. **Update client interface**: Ensure `GitHubClient` exposes both `get_as_app()` and `get()` methods
4. **Update authentication provider**: Ensure `AuthProvider` implements `app_token()` method
5. **Update tests**: Add tests for both authentication levels

## Summary


**Use App-Level Authentication (JWT) For**:

- Listing installations
- Managing installations
- Creating installation tokens
- App-level configuration
- Marketplace operations

**Use Installation-Level Authentication (Installation Token) For**:

- All repository operations
- Organization operations
- Webhook processing
- User operations
- Any operation on specific repositories

This two-level authentication model enables GitHub Apps to both manage their own installation lifecycle and perform repository-specific operations within appropriate security scopes.