github-app-forge 0.1.1

Declarative GitHub App lifecycle management via Manifest flow
Documentation
//! JWT generation for GitHub App authentication.
//!
//! After app creation we have the App ID + private key (PEM). For any API
//! call that operates on the App's identity (e.g. listing installations,
//! issuing installation access tokens), we sign a short-lived JWT with the
//! private key as RS256. GitHub then trades that JWT for an installation
//! access token scoped to a specific installation.

use anyhow::{Context, Result};
use chrono::Utc;
use jsonwebtoken::{encode, EncodingKey, Header};
use serde::Serialize;

#[derive(Debug, Serialize)]
struct Claims {
    iat: i64,
    exp: i64,
    iss: String,
}

/// Sign a JWT for the given app id + PEM private key. Lives 9 minutes
/// (GitHub's max is 10; subtract 1 for clock skew).
pub fn sign_app_jwt(app_id: u64, private_key_pem: &str) -> Result<String> {
    let now = Utc::now().timestamp();
    let claims = Claims {
        iat: now - 60, // 1 min back-dated for clock skew
        exp: now + 9 * 60,
        iss: app_id.to_string(),
    };
    let key = EncodingKey::from_rsa_pem(private_key_pem.as_bytes())
        .context("failed to parse RSA private key from PEM")?;
    encode(&Header::new(jsonwebtoken::Algorithm::RS256), &claims, &key)
        .context("failed to sign JWT")
}