v0.0.21: WZP integration groundwork — CallSignal + token validation
WZP-FC-1: CallSignal WireMessage variant - CallSignalType enum: Offer, Answer, IceCandidate, Hangup, Reject, Ringing, Busy - Routed through existing E2E encrypted channels - Server dedup handles new variant - TUI shows "📞 Call signal: Offer" etc - CLI recv prints call signals WZP-FC-4: Token validation endpoint - POST /v1/auth/validate { "token": "..." } - Returns: { "valid": true, "fingerprint": "...", "alias": "..." } - WZP relay calls this to verify featherChat bearer tokens - Resolves alias alongside fingerprint These two unblock WZP integration tasks WZP-S-2 (accept FC tokens) and WZP-S-3 (signaling bridge mode). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,7 @@ pub fn routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
.route("/auth/challenge", post(create_challenge))
|
||||
.route("/auth/verify", post(verify_challenge))
|
||||
.route("/auth/validate", post(validate_token_endpoint))
|
||||
}
|
||||
|
||||
fn now_ts() -> i64 {
|
||||
@@ -172,8 +173,6 @@ async fn verify_challenge(
|
||||
}
|
||||
|
||||
/// Validate a bearer token. Returns the fingerprint if valid.
|
||||
/// Used by protected endpoints (will be wired in when auth middleware is added).
|
||||
#[allow(dead_code)]
|
||||
pub fn validate_token(db: &sled::Tree, token: &str) -> Option<String> {
|
||||
let data = db.get(token.as_bytes()).ok()??;
|
||||
let val: serde_json::Value = serde_json::from_slice(&data).ok()?;
|
||||
@@ -184,3 +183,42 @@ pub fn validate_token(db: &sled::Tree, token: &str) -> Option<String> {
|
||||
}
|
||||
val.get("fingerprint")?.as_str().map(String::from)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ValidateRequest {
|
||||
token: String,
|
||||
}
|
||||
|
||||
/// External token validation endpoint — used by WarzonePhone and other services
|
||||
/// to verify that a bearer token is valid and get the associated fingerprint.
|
||||
///
|
||||
/// POST /v1/auth/validate { "token": "..." }
|
||||
/// Returns: { "valid": true, "fingerprint": "...", "expires_at": ... }
|
||||
/// or: { "valid": false }
|
||||
async fn validate_token_endpoint(
|
||||
State(state): State<AppState>,
|
||||
Json(req): Json<ValidateRequest>,
|
||||
) -> Json<serde_json::Value> {
|
||||
match validate_token(&state.db.tokens, &req.token) {
|
||||
Some(fingerprint) => {
|
||||
// Also resolve alias if available
|
||||
let alias = state.db.aliases.get(format!("fp:{}", fingerprint).as_bytes())
|
||||
.ok().flatten()
|
||||
.map(|v| String::from_utf8_lossy(&v).to_string());
|
||||
|
||||
// Get Ethereum address if we have the bundle
|
||||
let eth_address: Option<String> = None; // Would need seed, which server doesn't have
|
||||
|
||||
tracing::info!("Token validated for {}", fingerprint);
|
||||
Json(serde_json::json!({
|
||||
"valid": true,
|
||||
"fingerprint": fingerprint,
|
||||
"alias": alias,
|
||||
"eth_address": eth_address,
|
||||
}))
|
||||
}
|
||||
None => {
|
||||
Json(serde_json::json!({ "valid": false }))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ fn extract_message_id(data: &[u8]) -> Option<String> {
|
||||
WireMessage::SenderKeyDistribution { sender_fingerprint, group_name, .. } => {
|
||||
Some(format!("skd:{}:{}", sender_fingerprint, group_name))
|
||||
}
|
||||
WireMessage::CallSignal { id, .. } => Some(id),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
|
||||
@@ -34,6 +34,7 @@ fn extract_message_id(data: &[u8]) -> Option<String> {
|
||||
WireMessage::SenderKeyDistribution { sender_fingerprint, group_name, .. } => {
|
||||
Some(format!("skd:{}:{}", sender_fingerprint, group_name))
|
||||
}
|
||||
WireMessage::CallSignal { id, .. } => Some(id),
|
||||
}
|
||||
} else {
|
||||
None
|
||||
|
||||
Reference in New Issue
Block a user