huskies: merge 727_story_ed25519_node_identity_keypair_generation_persistence_and_identity_endpoint

This commit is contained in:
dave
2026-04-27 18:31:29 +00:00
parent b008235d0d
commit 80661fa622
6 changed files with 262 additions and 0 deletions
+62
View File
@@ -0,0 +1,62 @@
//! Node identity endpoint — exposes this node's Ed25519 public key.
//!
//! `GET /identity` returns the node's ID and public key as JSON. No
//! authentication is required; only the public half of the keypair is
//! disclosed.
use poem::handler;
use poem::web::Json;
use serde::Serialize;
/// JSON response body for `GET /identity`.
#[derive(Serialize)]
pub struct IdentityResponse {
/// Node ID: lowercase hex-encoding of the 32-byte Ed25519 public key.
pub node_id: String,
/// Lowercase hex-encoding of the 32-byte Ed25519 public key.
pub pubkey: String,
}
/// `GET /identity` — return this node's Ed25519 public key.
///
/// Returns `{"node_id": "<64-hex>", "pubkey": "<64-hex>"}`.
/// No authentication required; the private key is never exposed.
#[handler]
pub fn identity_handler() -> Json<IdentityResponse> {
match crate::node_identity::get_identity() {
Some(id) => Json(IdentityResponse {
node_id: id.node_id.clone(),
pubkey: id.pubkey_hex.clone(),
}),
None => Json(IdentityResponse {
node_id: "uninitialized".to_string(),
pubkey: "uninitialized".to_string(),
}),
}
}
#[cfg(test)]
mod tests {
use super::*;
use poem::{Route, get, test::TestClient};
#[tokio::test]
async fn identity_endpoint_returns_json() {
// Initialise a temporary key file so get_identity() returns Some.
let tmp = tempfile::tempdir().unwrap();
let key_path = tmp.path().join("node_identity.key");
crate::node_identity::init_identity(&key_path).unwrap();
let app = Route::new().at("/identity", get(identity_handler));
let cli = TestClient::new(app);
let resp = cli.get("/identity").send().await;
resp.assert_status_is_ok();
let body: serde_json::Value = resp.json().await.value().deserialize();
let node_id = body["node_id"].as_str().unwrap();
let pubkey = body["pubkey"].as_str().unwrap();
assert_eq!(node_id.len(), 64);
assert!(node_id.chars().all(|c| c.is_ascii_hexdigit()));
assert_eq!(node_id, pubkey);
}
}
+2
View File
@@ -9,6 +9,7 @@ pub mod chat;
pub mod context;
pub mod events;
pub mod health;
pub mod identity;
pub mod io;
pub mod mcp;
pub mod model;
@@ -92,6 +93,7 @@ pub fn build_routes(
post(mcp::mcp_post_handler).get(mcp::mcp_get_handler),
)
.at("/health", get(health::health))
.at("/identity", get(identity::identity_handler))
.at(
"/oauth/authorize",
get(oauth::oauth_authorize).data(oauth_state.clone()),