g1t

syntaqx/g1t

public

Git for AI scale: a forge for thousands of agents working on the same code at once.

g1t/crates/sshd/src/api.rs

100 lines2,687 bytes
//! Client for the g1t Worker's internal endpoints, which own all
//! authentication and authorization decisions.

use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, Deserialize)]
pub struct User {
    pub id: u64,
    pub username: String,
}

/// An Artifacts remote and a short-lived token scoped to one repo.
#[derive(Debug, Deserialize)]
pub struct Access {
    pub remote: String,
    pub token: String,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
pub enum Service {
    #[serde(rename = "git-upload-pack")]
    UploadPack,
    #[serde(rename = "git-receive-pack")]
    ReceivePack,
}

impl Service {
    pub fn as_str(self) -> &'static str {
        match self {
            Service::UploadPack => "git-upload-pack",
            Service::ReceivePack => "git-receive-pack",
        }
    }
}

#[derive(Deserialize)]
struct ErrorBody {
    error: String,
}

pub struct Api {
    base: String,
    secret: String,
    pub http: reqwest::Client,
}

impl Api {
    pub fn new(base: String, secret: String) -> Self {
        Self {
            base,
            secret,
            http: reqwest::Client::new(),
        }
    }

    /// The user who registered the key with this SHA-256 fingerprint.
    pub async fn user_for_key(&self, fingerprint: &str) -> Result<Option<User>> {
        let response = self
            .http
            .post(format!("{}/_internal/ssh/user", self.base))
            .bearer_auth(&self.secret)
            .json(&serde_json::json!({ "fingerprint": fingerprint }))
            .send()
            .await
            .context("key lookup failed")?;
        if response.status() == reqwest::StatusCode::NOT_FOUND {
            return Ok(None);
        }
        Ok(Some(response.error_for_status()?.json().await?))
    }

    /// `Ok(Err(message))` is a refusal to show the user.
    pub async fn access(
        &self,
        user: &User,
        owner: &str,
        repo: &str,
        service: Service,
    ) -> Result<Result<Access, String>> {
        let response = self
            .http
            .post(format!("{}/_internal/ssh/access", self.base))
            .bearer_auth(&self.secret)
            .json(&serde_json::json!({
                "user_id": user.id,
                "owner": owner,
                "repo": repo,
                "service": service,
            }))
            .send()
            .await
            .context("access check failed")?;
        if response.status().is_client_error() {
            let body: ErrorBody = response.json().await?;
            return Ok(Err(body.error));
        }
        Ok(Ok(response.error_for_status()?.json().await?))
    }
}