mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Revert "fix(cli,relay): resolve agents by verified owner" (#3168)
Reverts block/buzz#2615
This commit is contained in:
@@ -56,7 +56,6 @@ buzz reactions get --event <event-id>
|
||||
buzz users get # your own profile
|
||||
buzz users get --pubkey <hex> # single user
|
||||
buzz users get --pubkey <hex> --pubkey <hex> # batch (max 200)
|
||||
buzz users get --name Honey --owner me # exact-name lookup scoped to your verified owner
|
||||
buzz users set-presence --status online
|
||||
|
||||
# DMs
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use nostr::PublicKey;
|
||||
|
||||
use crate::client::{normalize_write_response, BuzzClient};
|
||||
use crate::error::CliError;
|
||||
use crate::validate::validate_hex64;
|
||||
@@ -15,7 +13,6 @@ pub async fn cmd_get_users(
|
||||
client: &BuzzClient,
|
||||
pubkeys: &[String],
|
||||
name: Option<&str>,
|
||||
owner: Option<&str>,
|
||||
format: &crate::OutputFormat,
|
||||
) -> Result<(), CliError> {
|
||||
if let Some(query) = name {
|
||||
@@ -24,7 +21,7 @@ pub async fn cmd_get_users(
|
||||
"--name and --pubkey are mutually exclusive".into(),
|
||||
));
|
||||
}
|
||||
return search_by_name(client, query, owner, format).await;
|
||||
return search_by_name(client, query, format).await;
|
||||
}
|
||||
|
||||
for pk in pubkeys {
|
||||
@@ -41,18 +38,12 @@ pub async fn cmd_get_users(
|
||||
pubkeys.iter().map(|s| s.as_str()).collect()
|
||||
};
|
||||
|
||||
let owner = resolve_owner(client, owner)?;
|
||||
let filter = serde_json::json!({
|
||||
"kinds": [0],
|
||||
"authors": authors,
|
||||
"limit": authors.len(),
|
||||
"include_agent_owner": true,
|
||||
"agent_owner": owner,
|
||||
"limit": authors.len()
|
||||
});
|
||||
let resp = client.query(&filter).await?;
|
||||
let my_owner_pubkey = client
|
||||
.auth_tag_owner_hex()
|
||||
.unwrap_or_else(|| client.keys().public_key().to_hex());
|
||||
let events: Vec<serde_json::Value> = serde_json::from_str(&resp).unwrap_or_default();
|
||||
let profiles: Vec<serde_json::Value> = events
|
||||
.iter()
|
||||
@@ -64,7 +55,6 @@ pub async fn cmd_get_users(
|
||||
"pubkey".to_string(),
|
||||
serde_json::json!(e.get("pubkey").and_then(|v| v.as_str()).unwrap_or("")),
|
||||
);
|
||||
copy_owner_fields(e, obj, &my_owner_pubkey);
|
||||
}
|
||||
Some(profile)
|
||||
})
|
||||
@@ -76,9 +66,6 @@ pub async fn cmd_get_users(
|
||||
.map(|p| serde_json::json!({
|
||||
"pubkey": p.get("pubkey").cloned().unwrap_or_default(),
|
||||
"display_name": p.get("display_name").or_else(|| p.get("name")).cloned().unwrap_or_default(),
|
||||
"owner_pubkey": p.get("owner_pubkey").cloned().unwrap_or_default(),
|
||||
"owner_display_name": p.get("owner_display_name").cloned().unwrap_or_default(),
|
||||
"owned_by_me": p.get("owned_by_me").cloned().unwrap_or_default(),
|
||||
}))
|
||||
.collect();
|
||||
serde_json::to_string(&compact).unwrap_or_default()
|
||||
@@ -89,70 +76,23 @@ pub async fn cmd_get_users(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn copy_owner_fields(
|
||||
event: &serde_json::Value,
|
||||
profile: &mut serde_json::Map<String, serde_json::Value>,
|
||||
my_owner_pubkey: &str,
|
||||
) {
|
||||
if let Some(value) = event.get("agent_owner_pubkey") {
|
||||
profile.insert("owner_pubkey".to_string(), value.clone());
|
||||
}
|
||||
if let Some(value) = event.get("agent_owner_display_name") {
|
||||
profile.insert("owner_display_name".to_string(), value.clone());
|
||||
}
|
||||
if let Some(owner) = event
|
||||
.get("agent_owner_pubkey")
|
||||
.and_then(|value| value.as_str())
|
||||
{
|
||||
profile.insert(
|
||||
"owned_by_me".to_string(),
|
||||
serde_json::json!(owner == my_owner_pubkey),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_owner(client: &BuzzClient, owner: Option<&str>) -> Result<Option<String>, CliError> {
|
||||
owner
|
||||
.map(|owner| {
|
||||
if owner == "me" {
|
||||
Ok(client
|
||||
.auth_tag_owner_hex()
|
||||
.unwrap_or_else(|| client.keys().public_key().to_hex()))
|
||||
} else {
|
||||
PublicKey::parse(owner)
|
||||
.map(|pubkey| pubkey.to_hex())
|
||||
.map_err(|e| {
|
||||
CliError::Usage(format!("--owner must be `me`, a pubkey, or npub: {e}"))
|
||||
})
|
||||
}
|
||||
})
|
||||
.transpose()
|
||||
}
|
||||
|
||||
/// Search for users by display name via NIP-50 full-text search on kind:0 profiles.
|
||||
/// Returns [] if the relay does not implement NIP-50 search.
|
||||
async fn search_by_name(
|
||||
client: &BuzzClient,
|
||||
query: &str,
|
||||
owner: Option<&str>,
|
||||
format: &crate::OutputFormat,
|
||||
) -> Result<(), CliError> {
|
||||
if query.trim().is_empty() {
|
||||
return Err(CliError::Usage("--name cannot be empty".into()));
|
||||
}
|
||||
|
||||
let owner = resolve_owner(client, owner)?;
|
||||
let filter = serde_json::json!({
|
||||
"kinds": [0],
|
||||
"search": query,
|
||||
"limit": 100,
|
||||
"include_agent_owner": true,
|
||||
"agent_owner": owner,
|
||||
"limit": 100
|
||||
});
|
||||
let raw = client.query(&filter).await?;
|
||||
let my_owner_pubkey = client
|
||||
.auth_tag_owner_hex()
|
||||
.unwrap_or_else(|| client.keys().public_key().to_hex());
|
||||
|
||||
// Parse and filter client-side for case-insensitive substring match
|
||||
// on display_name or name fields (NIP-50 may return broader matches).
|
||||
@@ -186,7 +126,6 @@ async fn search_by_name(
|
||||
"pubkey".to_string(),
|
||||
serde_json::json!(event.get("pubkey").and_then(|v| v.as_str()).unwrap_or("")),
|
||||
);
|
||||
copy_owner_fields(event, obj, &my_owner_pubkey);
|
||||
}
|
||||
Some(profile)
|
||||
})
|
||||
@@ -198,9 +137,6 @@ async fn search_by_name(
|
||||
.map(|p| serde_json::json!({
|
||||
"pubkey": p.get("pubkey").cloned().unwrap_or_default(),
|
||||
"display_name": p.get("display_name").or_else(|| p.get("name")).cloned().unwrap_or_default(),
|
||||
"owner_pubkey": p.get("owner_pubkey").cloned().unwrap_or_default(),
|
||||
"owner_display_name": p.get("owner_display_name").cloned().unwrap_or_default(),
|
||||
"owned_by_me": p.get("owned_by_me").cloned().unwrap_or_default(),
|
||||
}))
|
||||
.collect();
|
||||
serde_json::to_string(&compact).unwrap_or_default()
|
||||
@@ -375,11 +311,9 @@ pub async fn dispatch(
|
||||
) -> Result<(), CliError> {
|
||||
use crate::UsersCmd;
|
||||
match cmd {
|
||||
UsersCmd::Get {
|
||||
pubkeys,
|
||||
name,
|
||||
owner,
|
||||
} => cmd_get_users(client, &pubkeys, name.as_deref(), owner.as_deref(), format).await,
|
||||
UsersCmd::Get { pubkeys, name } => {
|
||||
cmd_get_users(client, &pubkeys, name.as_deref(), format).await
|
||||
}
|
||||
UsersCmd::SetProfile {
|
||||
name,
|
||||
avatar,
|
||||
@@ -402,30 +336,9 @@ pub async fn dispatch(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{copy_owner_fields, presence_subject};
|
||||
use super::presence_subject;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn copy_owner_fields_marks_authenticated_owner() {
|
||||
let owner = "a".repeat(64);
|
||||
let event = json!({
|
||||
"agent_owner_pubkey": owner,
|
||||
"agent_owner_display_name": "John",
|
||||
});
|
||||
let mut profile = serde_json::Map::new();
|
||||
copy_owner_fields(&event, &mut profile, &"a".repeat(64));
|
||||
assert_eq!(profile["owner_display_name"], "John");
|
||||
assert_eq!(profile["owned_by_me"], true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn copy_owner_fields_marks_other_owner() {
|
||||
let event = json!({"agent_owner_pubkey": "a".repeat(64)});
|
||||
let mut profile = serde_json::Map::new();
|
||||
copy_owner_fields(&event, &mut profile, &"b".repeat(64));
|
||||
assert_eq!(profile["owned_by_me"], false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn presence_subject_uses_p_tag() {
|
||||
let event = json!({"pubkey": "relay", "tags": [["p", "user"]]});
|
||||
|
||||
@@ -808,9 +808,6 @@ pub enum UsersCmd {
|
||||
/// Search by display name (case-insensitive substring match)
|
||||
#[arg(long = "name")]
|
||||
name: Option<String>,
|
||||
/// Filter agents by verified owner (`me`, 64-char hex, or npub)
|
||||
#[arg(long = "owner")]
|
||||
owner: Option<String>,
|
||||
},
|
||||
/// Update the current identity's profile
|
||||
#[command(name = "set-profile")]
|
||||
|
||||
@@ -1843,24 +1843,6 @@ impl Db {
|
||||
user::search_users(&self.pool, community_id, query, limit).await
|
||||
}
|
||||
|
||||
/// Return agent pubkeys whose verified owner matches `owner_pubkey`.
|
||||
pub async fn list_agent_pubkeys_by_owner(
|
||||
&self,
|
||||
community_id: CommunityId,
|
||||
owner_pubkey: &[u8],
|
||||
) -> Result<Vec<Vec<u8>>> {
|
||||
user::list_agent_pubkeys_by_owner(&self.pool, community_id, owner_pubkey).await
|
||||
}
|
||||
|
||||
/// Fetch verified ownership metadata for the requested agent pubkeys.
|
||||
pub async fn get_agent_owners(
|
||||
&self,
|
||||
community_id: CommunityId,
|
||||
agent_pubkeys: &[Vec<u8>],
|
||||
) -> Result<Vec<user::AgentOwner>> {
|
||||
user::get_agent_owners(&self.pool, community_id, agent_pubkeys).await
|
||||
}
|
||||
|
||||
/// Atomically set agent owner — only if no owner is currently assigned.
|
||||
/// Returns Ok(true) if set, Ok(false) if an owner already exists.
|
||||
pub async fn set_agent_owner(
|
||||
|
||||
@@ -560,7 +560,7 @@ mod tests {
|
||||
let mut migrations: Vec<_> = MIGRATOR.iter().collect();
|
||||
migrations.sort_by_key(|migration| migration.version);
|
||||
|
||||
assert_eq!(migrations.len(), 25);
|
||||
assert_eq!(migrations.len(), 24);
|
||||
assert_eq!(migrations[0].version, 1);
|
||||
assert_eq!(&*migrations[0].description, "initial schema");
|
||||
assert!(migrations[0]
|
||||
|
||||
@@ -5,17 +5,6 @@ use buzz_core::CommunityId;
|
||||
use sqlx::PgPool;
|
||||
use sqlx::Row;
|
||||
|
||||
/// Ownership metadata for an agent profile.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct AgentOwner {
|
||||
/// Raw agent public key.
|
||||
pub agent_pubkey: Vec<u8>,
|
||||
/// Raw owner public key, verified when the agent authenticated via NIP-OA.
|
||||
pub owner_pubkey: Vec<u8>,
|
||||
/// Owner's current display name, when available.
|
||||
pub owner_display_name: Option<String>,
|
||||
}
|
||||
|
||||
/// A user's profile fields.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UserProfile {
|
||||
@@ -291,65 +280,6 @@ pub async fn search_users(
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Return agent pubkeys whose verified owner matches `owner_pubkey`.
|
||||
pub async fn list_agent_pubkeys_by_owner(
|
||||
pool: &PgPool,
|
||||
community_id: CommunityId,
|
||||
owner_pubkey: &[u8],
|
||||
) -> Result<Vec<Vec<u8>>> {
|
||||
let rows = sqlx::query_scalar::<_, Vec<u8>>(
|
||||
r#"
|
||||
SELECT pubkey
|
||||
FROM users
|
||||
WHERE community_id = $1 AND agent_owner_pubkey = $2
|
||||
"#,
|
||||
)
|
||||
.bind(community_id.as_uuid())
|
||||
.bind(owner_pubkey)
|
||||
.fetch_all(pool)
|
||||
.await?;
|
||||
Ok(rows)
|
||||
}
|
||||
|
||||
/// Fetch verified ownership metadata for the requested agent pubkeys.
|
||||
pub async fn get_agent_owners(
|
||||
pool: &PgPool,
|
||||
community_id: CommunityId,
|
||||
agent_pubkeys: &[Vec<u8>],
|
||||
) -> Result<Vec<AgentOwner>> {
|
||||
if agent_pubkeys.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let rows = sqlx::query_as::<_, (Vec<u8>, Vec<u8>, Option<String>)>(
|
||||
r#"
|
||||
SELECT agent.pubkey, agent.agent_owner_pubkey, owner.display_name
|
||||
FROM users agent
|
||||
LEFT JOIN users owner
|
||||
ON owner.community_id = agent.community_id
|
||||
AND owner.pubkey = agent.agent_owner_pubkey
|
||||
WHERE agent.community_id = $1
|
||||
AND agent.pubkey = ANY($2)
|
||||
AND agent.agent_owner_pubkey IS NOT NULL
|
||||
"#,
|
||||
)
|
||||
.bind(community_id.as_uuid())
|
||||
.bind(agent_pubkeys)
|
||||
.fetch_all(pool)
|
||||
.await?;
|
||||
|
||||
Ok(rows
|
||||
.into_iter()
|
||||
.map(
|
||||
|(agent_pubkey, owner_pubkey, owner_display_name)| AgentOwner {
|
||||
agent_pubkey,
|
||||
owner_pubkey,
|
||||
owner_display_name,
|
||||
},
|
||||
)
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// Set the owner pubkey for an agent user.
|
||||
/// The owner pubkey must already exist in the users table (FK constraint).
|
||||
/// Returns an error if the agent pubkey is not found (rows_affected == 0).
|
||||
@@ -534,57 +464,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "requires Postgres"]
|
||||
async fn test_agent_owner_queries_are_scoped_and_include_display_name() {
|
||||
let db = setup_db().await;
|
||||
let community = make_community(&db.pool).await;
|
||||
let agent_pk = random_pubkey();
|
||||
let other_agent_pk = random_pubkey();
|
||||
let owner_pk = random_pubkey();
|
||||
let other_owner_pk = random_pubkey();
|
||||
for pubkey in [&agent_pk, &other_agent_pk, &owner_pk, &other_owner_pk] {
|
||||
ensure_user(&db.pool, community, pubkey).await.unwrap();
|
||||
}
|
||||
update_user_profile(
|
||||
&db.pool,
|
||||
community,
|
||||
&owner_pk,
|
||||
Some("Owner Name"),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
set_agent_owner(&db.pool, community, &agent_pk, &owner_pk)
|
||||
.await
|
||||
.unwrap();
|
||||
set_agent_owner(&db.pool, community, &other_agent_pk, &other_owner_pk)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
list_agent_pubkeys_by_owner(&db.pool, community, &owner_pk)
|
||||
.await
|
||||
.unwrap(),
|
||||
vec![agent_pk.clone()]
|
||||
);
|
||||
assert_eq!(
|
||||
get_agent_owners(&db.pool, community, &[agent_pk.clone(), other_agent_pk],)
|
||||
.await
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.find(|owner| owner.agent_pubkey == agent_pk)
|
||||
.unwrap(),
|
||||
AgentOwner {
|
||||
agent_pubkey: agent_pk,
|
||||
owner_pubkey: owner_pk,
|
||||
owner_display_name: Some("Owner Name".into()),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/// set_channel_add_policy should persist each of the three valid policies.
|
||||
#[tokio::test]
|
||||
#[ignore = "requires Postgres"]
|
||||
|
||||
@@ -280,112 +280,6 @@ fn extension_flag(raw: &Value, key: &str) -> bool {
|
||||
raw.get(key).and_then(Value::as_bool).unwrap_or(false)
|
||||
}
|
||||
|
||||
fn extract_agent_owner(raw: &Value) -> Result<Option<Vec<u8>>, (StatusCode, Json<Value>)> {
|
||||
let Some(value) = raw.get("agent_owner") else {
|
||||
return Ok(None);
|
||||
};
|
||||
if value.is_null() {
|
||||
return Ok(None);
|
||||
}
|
||||
let owner = value.as_str().ok_or_else(|| {
|
||||
api_error(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"agent_owner must be a 64-char hex pubkey",
|
||||
)
|
||||
})?;
|
||||
let bytes = hex::decode(owner)
|
||||
.ok()
|
||||
.filter(|bytes| bytes.len() == 32)
|
||||
.ok_or_else(|| {
|
||||
api_error(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"agent_owner must be a 64-char hex pubkey",
|
||||
)
|
||||
})?;
|
||||
Ok(Some(bytes))
|
||||
}
|
||||
|
||||
fn include_agent_owner(raw_filters: &[Value]) -> bool {
|
||||
raw_filters
|
||||
.iter()
|
||||
.any(|raw| extension_flag(raw, "include_agent_owner"))
|
||||
}
|
||||
|
||||
fn apply_agent_owner_authors(raw: &mut Value, owned_pubkeys: &[Vec<u8>]) {
|
||||
let mut owned_hex: std::collections::HashSet<String> =
|
||||
owned_pubkeys.iter().map(hex::encode).collect();
|
||||
if let Some(requested) = raw.get("authors").and_then(Value::as_array) {
|
||||
let requested: std::collections::HashSet<&str> =
|
||||
requested.iter().filter_map(Value::as_str).collect();
|
||||
owned_hex.retain(|pubkey| requested.contains(pubkey.as_str()));
|
||||
}
|
||||
raw["authors"] = Value::Array(owned_hex.into_iter().map(Value::String).collect());
|
||||
}
|
||||
|
||||
fn reject_unsupported_agent_owner_filter(raw: &Value) -> Result<(), (StatusCode, Json<Value>)> {
|
||||
if raw.get("agent_owner").is_some()
|
||||
&& (extension_flag(raw, "top_level")
|
||||
|| raw.get("feed_types").is_some()
|
||||
|| raw.get("depth_limit").is_some())
|
||||
{
|
||||
return Err(api_error(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"agent_owner is not supported with channel-window, feed, or thread filters",
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn enrich_agent_owners(
|
||||
state: &AppState,
|
||||
tenant: &buzz_core::tenant::TenantContext,
|
||||
events: &mut [Value],
|
||||
) -> Result<(), (StatusCode, Json<Value>)> {
|
||||
let pubkeys: Vec<Vec<u8>> = events
|
||||
.iter()
|
||||
.filter_map(|event| event.get("pubkey").and_then(Value::as_str))
|
||||
.filter_map(|pubkey| hex::decode(pubkey).ok())
|
||||
.collect();
|
||||
let owners = state
|
||||
.db
|
||||
.get_agent_owners(tenant.community(), &pubkeys)
|
||||
.await
|
||||
.map_err(|e| internal_error(&format!("agent owner lookup error: {e}")))?;
|
||||
let owners: std::collections::HashMap<Vec<u8>, buzz_db::user::AgentOwner> = owners
|
||||
.into_iter()
|
||||
.map(|owner| (owner.agent_pubkey.clone(), owner))
|
||||
.collect();
|
||||
|
||||
for event in events {
|
||||
let Some(agent_pubkey) = event
|
||||
.get("pubkey")
|
||||
.and_then(Value::as_str)
|
||||
.and_then(|pubkey| hex::decode(pubkey).ok())
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let Some(owner) = owners.get(&agent_pubkey) else {
|
||||
continue;
|
||||
};
|
||||
let Some(object) = event.as_object_mut() else {
|
||||
continue;
|
||||
};
|
||||
object.insert(
|
||||
"agent_owner_pubkey".into(),
|
||||
Value::String(hex::encode(&owner.owner_pubkey)),
|
||||
);
|
||||
object.insert(
|
||||
"agent_owner_display_name".into(),
|
||||
owner
|
||||
.owner_display_name
|
||||
.clone()
|
||||
.map(Value::String)
|
||||
.unwrap_or(Value::Null),
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn extract_depth_limit(raw: &Value) -> Option<u32> {
|
||||
raw.get("depth_limit")?
|
||||
.as_u64()
|
||||
@@ -1073,20 +967,8 @@ async fn query_events_authed(
|
||||
|
||||
// Two-pass parse: preserve raw JSON for custom extension fields (before_id,
|
||||
// depth_limit, feed_types) that nostr::Filter silently drops.
|
||||
let mut raw_filters: Vec<Value> = serde_json::from_slice(body)
|
||||
let raw_filters: Vec<Value> = serde_json::from_slice(body)
|
||||
.map_err(|e| api_error(StatusCode::BAD_REQUEST, &format!("invalid filters: {e}")))?;
|
||||
for raw in &mut raw_filters {
|
||||
reject_unsupported_agent_owner_filter(raw)?;
|
||||
let Some(owner_pubkey) = extract_agent_owner(raw)? else {
|
||||
continue;
|
||||
};
|
||||
let owned_pubkeys = state
|
||||
.db
|
||||
.list_agent_pubkeys_by_owner(tenant.community(), &owner_pubkey)
|
||||
.await
|
||||
.map_err(|e| internal_error(&format!("agent owner filter error: {e}")))?;
|
||||
apply_agent_owner_authors(raw, &owned_pubkeys);
|
||||
}
|
||||
let filters: Vec<nostr::Filter> = raw_filters
|
||||
.iter()
|
||||
.map(|v| serde_json::from_value(v.clone()))
|
||||
@@ -1323,14 +1205,6 @@ async fn query_events_authed(
|
||||
if handled.contains(&idx) {
|
||||
continue;
|
||||
}
|
||||
if raw.get("agent_owner").is_some()
|
||||
&& raw
|
||||
.get("authors")
|
||||
.and_then(Value::as_array)
|
||||
.is_some_and(Vec::is_empty)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(ch_id) = extract_channel_from_filter(filter) {
|
||||
if !accessible_channels.contains(&ch_id) {
|
||||
@@ -1432,9 +1306,6 @@ async fn query_events_authed(
|
||||
}
|
||||
}
|
||||
|
||||
if include_agent_owner(&raw_filters) {
|
||||
enrich_agent_owners(state, tenant, &mut events).await?;
|
||||
}
|
||||
Ok(Json(Value::Array(events)))
|
||||
}
|
||||
|
||||
@@ -1779,14 +1650,6 @@ async fn handle_bridge_search(
|
||||
for (raw, filter) in raw_filters.iter().zip(filters) {
|
||||
let search_mode = extract_search_mode(raw);
|
||||
let search_page = extract_search_page(raw);
|
||||
if raw.get("agent_owner").is_some()
|
||||
&& raw
|
||||
.get("authors")
|
||||
.and_then(Value::as_array)
|
||||
.is_some_and(Vec::is_empty)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let search_text = match &filter.search {
|
||||
Some(s) if !s.is_empty() => s.clone(),
|
||||
_ => continue,
|
||||
@@ -1900,9 +1763,6 @@ async fn handle_bridge_search(
|
||||
}
|
||||
}
|
||||
|
||||
if include_agent_owner(raw_filters) {
|
||||
enrich_agent_owners(state, tenant, &mut events).await?;
|
||||
}
|
||||
Ok(Json(Value::Array(events)))
|
||||
}
|
||||
|
||||
@@ -2928,66 +2788,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_agent_owner_accepts_hex_and_rejects_invalid_values() {
|
||||
let owner = "a".repeat(64);
|
||||
assert_eq!(
|
||||
extract_agent_owner(&serde_json::json!({"agent_owner": owner}))
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
vec![0xaa; 32]
|
||||
);
|
||||
assert!(extract_agent_owner(&serde_json::json!({"agent_owner": "bad"})).is_err());
|
||||
assert!(extract_agent_owner(&serde_json::json!({"agent_owner": true})).is_err());
|
||||
assert!(extract_agent_owner(&serde_json::json!({"agent_owner": 42})).is_err());
|
||||
assert_eq!(
|
||||
extract_agent_owner(&serde_json::json!({"agent_owner": null})).unwrap(),
|
||||
None
|
||||
);
|
||||
assert_eq!(extract_agent_owner(&serde_json::json!({})).unwrap(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn agent_owner_authors_fail_closed_and_intersect_requested_authors() {
|
||||
let agent_a = vec![0xaa; 32];
|
||||
let agent_b = vec![0xbb; 32];
|
||||
let owned = vec![agent_a.clone(), agent_b];
|
||||
|
||||
let mut unscoped = serde_json::json!({});
|
||||
apply_agent_owner_authors(&mut unscoped, &owned);
|
||||
let authors = unscoped["authors"].as_array().unwrap();
|
||||
assert_eq!(authors.len(), 2);
|
||||
assert!(authors.contains(&serde_json::json!(hex::encode(&agent_a))));
|
||||
|
||||
let mut intersected = serde_json::json!({
|
||||
"authors": [hex::encode(&agent_a), "cc".repeat(32)]
|
||||
});
|
||||
apply_agent_owner_authors(&mut intersected, &owned);
|
||||
assert_eq!(
|
||||
intersected["authors"],
|
||||
serde_json::json!([hex::encode(agent_a)])
|
||||
);
|
||||
|
||||
let mut empty = serde_json::json!({"authors": ["cc".repeat(32)]});
|
||||
apply_agent_owner_authors(&mut empty, &owned);
|
||||
assert_eq!(empty["authors"], serde_json::json!([]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn agent_owner_rejects_specialized_filters_that_cannot_enforce_authors() {
|
||||
for raw in [
|
||||
serde_json::json!({"agent_owner": "aa".repeat(32), "top_level": true}),
|
||||
serde_json::json!({"agent_owner": "aa".repeat(32), "feed_types": ["activity"]}),
|
||||
serde_json::json!({"agent_owner": "aa".repeat(32), "depth_limit": 1}),
|
||||
] {
|
||||
assert!(reject_unsupported_agent_owner_filter(&raw).is_err());
|
||||
}
|
||||
assert!(reject_unsupported_agent_owner_filter(
|
||||
&serde_json::json!({"agent_owner": "aa".repeat(32), "kinds": [0]})
|
||||
)
|
||||
.is_ok());
|
||||
}
|
||||
|
||||
/// `nip42_expected_relay_url` derives scheme from `config_relay_url`'s
|
||||
/// prefix: `wss://` → `wss`, everything else → `ws`. Deployments that run
|
||||
/// `ws://` in dev/test must produce a `ws://` URL that matches what
|
||||
@@ -3555,36 +3355,6 @@ mod tests {
|
||||
|
||||
/// Drive a single POST /events request through the router and return the
|
||||
/// HTTP status code.
|
||||
async fn post_query(
|
||||
state: Arc<crate::state::AppState>,
|
||||
host: &str,
|
||||
pubkey_hex: &str,
|
||||
body: &[u8],
|
||||
) -> (axum::http::StatusCode, Value) {
|
||||
use axum::body::{to_bytes, Body};
|
||||
use axum::http::{header, Request};
|
||||
use tower::ServiceExt;
|
||||
|
||||
let response = crate::router::build_router(state)
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.method("POST")
|
||||
.uri("/query")
|
||||
.header(header::HOST, host)
|
||||
.header("x-pubkey", pubkey_hex)
|
||||
.body(Body::from(body.to_vec()))
|
||||
.expect("build request"),
|
||||
)
|
||||
.await
|
||||
.expect("router oneshot");
|
||||
let status = response.status();
|
||||
let bytes = to_bytes(response.into_body(), usize::MAX)
|
||||
.await
|
||||
.expect("read response body");
|
||||
let body = serde_json::from_slice(&bytes).expect("parse response JSON");
|
||||
(status, body)
|
||||
}
|
||||
|
||||
async fn post_events(
|
||||
state: Arc<crate::state::AppState>,
|
||||
host: &str,
|
||||
@@ -3610,102 +3380,6 @@ mod tests {
|
||||
.status()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "requires Postgres and Redis"]
|
||||
async fn query_agent_owner_returns_only_verified_owner_matches() {
|
||||
let state = bridge_handler_test_state()
|
||||
.await
|
||||
.expect("local Postgres and Redis required");
|
||||
let host = format!("bridge-owner-{}.local", uuid::Uuid::new_v4().simple());
|
||||
let community = state
|
||||
.db
|
||||
.ensure_configured_community(&host)
|
||||
.await
|
||||
.expect("ensure community")
|
||||
.id;
|
||||
let requester = Keys::generate();
|
||||
let owner_a = Keys::generate();
|
||||
let owner_b = Keys::generate();
|
||||
let agent_a = Keys::generate();
|
||||
let agent_b = Keys::generate();
|
||||
|
||||
for keys in [&requester, &owner_a, &owner_b, &agent_a, &agent_b] {
|
||||
state
|
||||
.db
|
||||
.ensure_user(community, &keys.public_key().to_bytes())
|
||||
.await
|
||||
.expect("ensure user");
|
||||
}
|
||||
state
|
||||
.db
|
||||
.set_agent_owner(
|
||||
community,
|
||||
&agent_a.public_key().to_bytes(),
|
||||
&owner_a.public_key().to_bytes(),
|
||||
)
|
||||
.await
|
||||
.expect("set first owner");
|
||||
state
|
||||
.db
|
||||
.set_agent_owner(
|
||||
community,
|
||||
&agent_b.public_key().to_bytes(),
|
||||
&owner_b.public_key().to_bytes(),
|
||||
)
|
||||
.await
|
||||
.expect("set second owner");
|
||||
|
||||
for keys in [&agent_a, &agent_b] {
|
||||
let event = EventBuilder::new(Kind::Metadata, r#"{"display_name":"Honey"}"#)
|
||||
.sign_with_keys(keys)
|
||||
.expect("sign profile");
|
||||
state
|
||||
.db
|
||||
.insert_event(community, &event, None)
|
||||
.await
|
||||
.expect("insert profile");
|
||||
}
|
||||
|
||||
let filter = serde_json::json!([{
|
||||
"kinds": [0],
|
||||
"search": "Honey",
|
||||
"agent_owner": owner_a.public_key().to_hex(),
|
||||
"include_agent_owner": true,
|
||||
"limit": 100
|
||||
}]);
|
||||
let (status, body) = post_query(
|
||||
state.clone(),
|
||||
&host,
|
||||
&requester.public_key().to_hex(),
|
||||
filter.to_string().as_bytes(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::OK, "query response: {body}");
|
||||
let results = body.as_array().expect("array response");
|
||||
assert_eq!(results.len(), 1);
|
||||
assert_eq!(results[0]["pubkey"], agent_a.public_key().to_hex());
|
||||
assert_eq!(
|
||||
results[0]["agent_owner_pubkey"],
|
||||
owner_a.public_key().to_hex()
|
||||
);
|
||||
|
||||
let no_match = serde_json::json!([{
|
||||
"kinds": [0],
|
||||
"search": "Honey",
|
||||
"agent_owner": requester.public_key().to_hex(),
|
||||
"limit": 100
|
||||
}]);
|
||||
let (status, body) = post_query(
|
||||
state,
|
||||
&host,
|
||||
&requester.public_key().to_hex(),
|
||||
no_match.to_string().as_bytes(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(status, StatusCode::OK, "query response: {body}");
|
||||
assert_eq!(body, serde_json::json!([]));
|
||||
}
|
||||
|
||||
/// Collect buzz_events_rejected_total with (transport, reason) labels from
|
||||
/// a DebuggingRecorder snapshot.
|
||||
fn http_reject_counts(
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
CREATE INDEX idx_users_agent_owner
|
||||
ON users (community_id, agent_owner_pubkey)
|
||||
WHERE agent_owner_pubkey IS NOT NULL;
|
||||
Reference in New Issue
Block a user