137 lines
4.9 KiB
Rust
137 lines
4.9 KiB
Rust
use axum::{
|
|
extract::{Path, State},
|
|
routing::get,
|
|
Json, Router,
|
|
};
|
|
|
|
use crate::errors::AppResult;
|
|
use crate::state::AppState;
|
|
|
|
/// Convert a fingerprint to a per-bot unique numeric ID.
|
|
/// Hash(bot_token + user_fp) → i64. Different bots see different IDs for the same user.
|
|
/// This prevents cross-bot user correlation (same privacy model as Telegram).
|
|
pub fn fp_to_numeric_id_for_bot(fp: &str, bot_token: &str) -> i64 {
|
|
use sha2::{Sha256, Digest};
|
|
let mut hasher = Sha256::new();
|
|
hasher.update(bot_token.as_bytes());
|
|
hasher.update(b":");
|
|
hasher.update(fp.as_bytes());
|
|
let hash = hasher.finalize();
|
|
let mut arr = [0u8; 8];
|
|
arr.copy_from_slice(&hash[..8]);
|
|
i64::from_be_bytes(arr) & 0x7FFFFFFFFFFFFFFF // ensure positive
|
|
}
|
|
|
|
/// Convert a fingerprint hex string to a stable i64 ID (non-bot contexts).
|
|
/// Uses first 8 bytes of the fingerprint as a positive i64.
|
|
pub fn fp_to_numeric_id(fp: &str) -> i64 {
|
|
let clean: String = fp.chars().filter(|c| c.is_ascii_hexdigit()).take(16).collect();
|
|
let bytes = hex::decode(&clean).unwrap_or_default();
|
|
if bytes.len() >= 8 {
|
|
let mut arr = [0u8; 8];
|
|
arr.copy_from_slice(&bytes[..8]);
|
|
i64::from_be_bytes(arr) & 0x7FFFFFFFFFFFFFFF // ensure positive
|
|
} else {
|
|
0
|
|
}
|
|
}
|
|
|
|
pub fn routes() -> Router<AppState> {
|
|
Router::new().route("/resolve/:address", get(resolve_address))
|
|
}
|
|
|
|
/// Resolve an address to a fingerprint.
|
|
///
|
|
/// Accepts: ETH address (`0x...`), alias (`@name`), or raw fingerprint.
|
|
async fn resolve_address(
|
|
State(state): State<AppState>,
|
|
Path(address): Path<String>,
|
|
) -> AppResult<Json<serde_json::Value>> {
|
|
let addr = address.trim().to_lowercase();
|
|
|
|
// ETH address: 0x...
|
|
if addr.starts_with("0x") {
|
|
if let Some(fp_bytes) = state.db.eth_addresses.get(addr.as_bytes())? {
|
|
let fp = String::from_utf8_lossy(&fp_bytes).to_string();
|
|
return Ok(Json(serde_json::json!({
|
|
"address": address,
|
|
"fingerprint": fp,
|
|
"numeric_id": fp_to_numeric_id(&fp),
|
|
"type": "eth",
|
|
})));
|
|
}
|
|
// Try federation
|
|
if let Some(ref federation) = state.federation {
|
|
let url = format!("{}/v1/resolve/{}", federation.config.peer.url, addr);
|
|
if let Ok(resp) = federation.client.get(&url).send().await {
|
|
if resp.status().is_success() {
|
|
if let Ok(data) = resp.json::<serde_json::Value>().await {
|
|
if let Some(fp) = data.get("fingerprint").and_then(|v| v.as_str()) {
|
|
return Ok(Json(serde_json::json!({
|
|
"address": address,
|
|
"fingerprint": fp,
|
|
"numeric_id": fp_to_numeric_id(fp),
|
|
"type": "eth",
|
|
"federated": true,
|
|
})));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return Ok(Json(serde_json::json!({ "error": "address not found" })));
|
|
}
|
|
|
|
// Alias: @name
|
|
if addr.starts_with('@') {
|
|
let alias = &addr[1..];
|
|
// Try local alias resolution
|
|
let alias_key = format!("a:{}", alias);
|
|
if let Some(fp_bytes) = state.db.aliases.get(alias_key.as_bytes())? {
|
|
let fp = String::from_utf8_lossy(&fp_bytes).to_string();
|
|
return Ok(Json(serde_json::json!({
|
|
"address": address,
|
|
"fingerprint": fp,
|
|
"numeric_id": fp_to_numeric_id(&fp),
|
|
"type": "alias",
|
|
})));
|
|
}
|
|
// Try federation
|
|
if let Some(ref federation) = state.federation {
|
|
if let Some(fp) = federation.resolve_remote_alias(alias).await {
|
|
return Ok(Json(serde_json::json!({
|
|
"address": address,
|
|
"fingerprint": fp,
|
|
"numeric_id": fp_to_numeric_id(&fp),
|
|
"type": "alias",
|
|
"federated": true,
|
|
})));
|
|
}
|
|
}
|
|
return Ok(Json(serde_json::json!({ "error": "alias not found" })));
|
|
}
|
|
|
|
// Raw fingerprint: just echo back with optional reverse ETH lookup
|
|
let fp = addr
|
|
.chars()
|
|
.filter(|c| c.is_ascii_hexdigit())
|
|
.collect::<String>();
|
|
if fp.len() == 32 {
|
|
let rev_key = format!("rev:{}", fp);
|
|
let eth = state
|
|
.db
|
|
.eth_addresses
|
|
.get(rev_key.as_bytes())?
|
|
.map(|v| String::from_utf8_lossy(&v).to_string());
|
|
return Ok(Json(serde_json::json!({
|
|
"address": address,
|
|
"fingerprint": fp,
|
|
"numeric_id": fp_to_numeric_id(&fp),
|
|
"eth_address": eth,
|
|
"type": "fingerprint",
|
|
})));
|
|
}
|
|
|
|
Ok(Json(serde_json::json!({ "error": "unrecognized address format" })))
|
|
}
|