Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 32 additions & 5 deletions crates/gitlawb-node/src/api/agents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ use serde::{Deserialize, Serialize};
use crate::error::{AppError, Result};
use crate::state::AppState;

fn normalize_agent_did(did: &str) -> String {
if did.starts_with("did:") {
did.to_string()
} else {
format!("did:key:{did}")
}
}

#[derive(Debug, Serialize)]
pub struct TrustResponse {
pub did: String,
Expand Down Expand Up @@ -66,11 +74,12 @@ pub async fn show_agent(
State(state): State<AppState>,
Path(did): Path<String>,
) -> Result<(StatusCode, Json<AgentResponse>)> {
let normalized_did = normalize_agent_did(&did);
let agent = state
.db
.get_agent(&did)
.get_agent(&normalized_did)
.await?
.ok_or_else(|| AppError::NotFound(format!("agent {did} not found")))?;
.ok_or_else(|| AppError::NotFound(format!("agent {normalized_did} not found")))?;
Ok((
StatusCode::OK,
Json(AgentResponse {
Expand All @@ -88,14 +97,32 @@ pub async fn get_trust(
State(state): State<AppState>,
Path(did): Path<String>,
) -> Result<Json<TrustResponse>> {
let trust_score = state.db.get_trust_score(&did).await?;
let push_count = state.db.get_push_count(&did).await?;
let normalized_did = normalize_agent_did(&did);
let trust_score = state.db.get_trust_score(&normalized_did).await?;
let push_count = state.db.get_push_count(&normalized_did).await?;
let level = trust_level(trust_score);

Ok(Json(TrustResponse {
did,
did: normalized_did,
trust_score,
push_count,
level,
}))
}

#[cfg(test)]
mod tests {
use super::normalize_agent_did;

#[test]
fn normalize_agent_did_preserves_full_did() {
let did = "did:key:z6MkExample";

assert_eq!(normalize_agent_did(did), did);
}

#[test]
fn normalize_agent_did_expands_bare_key() {
assert_eq!(normalize_agent_did("z6MkExample"), "did:key:z6MkExample");
}
}
63 changes: 50 additions & 13 deletions crates/gl/src/cert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use clap::{Args, Subcommand};
use serde_json::Value;

use crate::http::NodeClient;
use crate::identity::load_keypair_from_dir;

#[derive(Args)]
pub struct CertArgs {
Expand Down Expand Up @@ -41,20 +42,25 @@ pub async fn run(args: CertArgs) -> Result<()> {
}
}

/// Resolve "repo" into (owner, name) — if no slash, use the node's own DID short form.
/// Resolve "repo" into (owner, name) using the caller's DID when no slash is given.
async fn resolve_repo(repo: &str, node: &str) -> Result<(String, String)> {
if let Some((owner, name)) = repo.split_once('/') {
Ok((owner.to_string(), name.to_string()))
} else {
let client = NodeClient::new(node, None);
let info: Value = client
.get("/")
.await?
.json()
.await
.context("failed to fetch node info")?;
let did = info["did"].as_str().context("node info missing 'did'")?;
let short = did.split(':').next_back().unwrap_or(did).to_string();
let short = if let Ok(kp) = load_keypair_from_dir(None) {
let did = kp.did().to_string();
did.split(':').next_back().unwrap_or(&did).to_string()
} else {
let client = NodeClient::new(node, None);
let info: Value = client
.get("/")
.await?
.json()
.await
.context("failed to fetch node info")?;
let did = info["did"].as_str().context("node info missing 'did'")?;
did.split(':').next_back().unwrap_or(did).to_string()
};
Ok((short, repo.to_string()))
}
}
Expand Down Expand Up @@ -94,15 +100,16 @@ async fn cmd_show(repo: String, id: String, node: String) -> Result<()> {
let (owner, name) = resolve_repo(&repo, &node).await?;

let client = NodeClient::new(&node, None);
let id = resolve_cert_id(&client, &owner, &name, &id).await?;

// Fetch the certificate
let path = format!("/api/v1/repos/{owner}/{name}/certs/{id}");
let cert: Value = client
let resp = client
.get(&path)
.await?
.json()
.await
.error_for_status()
.context("certificate not found")?;
let cert: Value = resp.json().await.context("certificate not found")?;

let cert_id = cert["id"].as_str().unwrap_or("?");
let ref_name = cert["ref_name"].as_str().unwrap_or("?");
Expand Down Expand Up @@ -155,3 +162,33 @@ async fn cmd_show(repo: String, id: String, node: String) -> Result<()> {

Ok(())
}

async fn resolve_cert_id(client: &NodeClient, owner: &str, name: &str, id: &str) -> Result<String> {
if id.len() >= 36 {
return Ok(id.to_string());
}

let path = format!("/api/v1/repos/{owner}/{name}/certs");
let resp: Value = client
.get(&path)
.await?
.error_for_status()
.context("failed to list certificates")?
.json()
.await
.context("failed to list certificates")?;

let certs = resp["certificates"].as_array().cloned().unwrap_or_default();
let matches: Vec<String> = certs
.iter()
.filter_map(|cert| cert["id"].as_str())
.filter(|cert_id| cert_id.starts_with(id))
.map(ToString::to_string)
.collect();

match matches.as_slice() {
[full_id] => Ok(full_id.to_string()),
[] => Ok(id.to_string()),
_ => anyhow::bail!("certificate prefix {id} matches multiple certificates"),
}
}