diff --git a/warzone/crates/warzone-client/src/cli/send.rs b/warzone/crates/warzone-client/src/cli/send.rs index ad8d8d3..c7d38ec 100644 --- a/warzone/crates/warzone-client/src/cli/send.rs +++ b/warzone/crates/warzone-client/src/cli/send.rs @@ -82,7 +82,7 @@ pub async fn run(recipient_fp: &str, message: &str, server_url: &str) -> Result< let encoded = bincode::serialize(&wire_msg) .context("failed to serialize wire message")?; - client.send_message(recipient_fp, &encoded).await?; + client.send_message(recipient_fp, Some(&our_pub.fingerprint.to_string()), &encoded).await?; println!("Message sent to {}", recipient_fp); Ok(()) diff --git a/warzone/crates/warzone-client/src/net.rs b/warzone/crates/warzone-client/src/net.rs index 42fa0f8..79184f5 100644 --- a/warzone/crates/warzone-client/src/net.rs +++ b/warzone/crates/warzone-client/src/net.rs @@ -19,6 +19,7 @@ struct RegisterRequest { #[derive(Serialize)] struct SendRequest { to: String, + from: Option, message: Vec, } @@ -93,12 +94,13 @@ impl ServerClient { } /// Send an encrypted message to the server for delivery. - pub async fn send_message(&self, to: &str, message: &[u8]) -> Result<()> { + pub async fn send_message(&self, to: &str, from: Option<&str>, message: &[u8]) -> Result<()> { let to_clean: String = to.chars().filter(|c| c.is_ascii_hexdigit()).collect(); self.client .post(format!("{}/v1/messages/send", self.base_url)) .json(&SendRequest { to: to_clean, + from: from.map(|f| f.chars().filter(|c| c.is_ascii_hexdigit()).collect()), message: message.to_vec(), }) .send() diff --git a/warzone/crates/warzone-client/src/tui/app.rs b/warzone/crates/warzone-client/src/tui/app.rs index 3729e2d..cc5afac 100644 --- a/warzone/crates/warzone-client/src/tui/app.rs +++ b/warzone/crates/warzone-client/src/tui/app.rs @@ -369,7 +369,7 @@ impl App { } }; - match client.send_message(&peer, &encoded).await { + match client.send_message(&peer, Some(&self.our_fp), &encoded).await { Ok(_) => { self.add_message(ChatLine { sender: self.our_fp[..12].to_string(), diff --git a/warzone/crates/warzone-server/src/routes/messages.rs b/warzone/crates/warzone-server/src/routes/messages.rs index fdf1906..e4091e6 100644 --- a/warzone/crates/warzone-server/src/routes/messages.rs +++ b/warzone/crates/warzone-server/src/routes/messages.rs @@ -8,6 +8,25 @@ use serde::Deserialize; use crate::errors::AppResult; use crate::state::AppState; +/// Touch the alias TTL for a fingerprint (renew on authenticated action). +fn renew_alias_ttl(db: &sled::Tree, fp: &str) { + let alias_key = format!("fp:{}", fp); + if let Ok(Some(alias_bytes)) = db.get(alias_key.as_bytes()) { + let alias = String::from_utf8_lossy(&alias_bytes).to_string(); + let rec_key = format!("rec:{}", alias); + if let Ok(Some(rec_data)) = db.get(rec_key.as_bytes()) { + if let Ok(mut record) = serde_json::from_slice::(&rec_data) { + if let Some(obj) = record.as_object_mut() { + obj.insert("last_active".into(), serde_json::json!(chrono::Utc::now().timestamp())); + if let Ok(updated) = serde_json::to_vec(&record) { + let _ = db.insert(rec_key.as_bytes(), updated); + } + } + } + } + } +} + pub fn routes() -> Router { Router::new() .route("/messages/send", post(send_message)) @@ -18,6 +37,8 @@ pub fn routes() -> Router { #[derive(Deserialize)] struct SendRequest { to: String, + #[serde(default)] + from: Option, message: Vec, } @@ -36,6 +57,12 @@ async fn send_message( let key = format!("queue:{}:{}", to, uuid::Uuid::new_v4()); tracing::info!("Queuing message for {} ({} bytes)", to, req.message.len()); state.db.messages.insert(key.as_bytes(), req.message)?; + + // Renew sender's alias TTL (sending = authenticated action) + if let Some(ref from) = req.from { + renew_alias_ttl(&state.db.aliases, &normalize_fp(from)); + } + Ok(Json(serde_json::json!({ "ok": true }))) }