use affinidi_tdk::didcomm::Message;
use affinidi_tdk::didcomm::message::pack;
use affinidi_tdk::messaging::protocols::discover_features::DiscoverFeatures;
use affinidi_tdk::messaging::protocols::trust_ping::TrustPing;
use axum::extract::State;
use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use did_hosting_common::didcomm_types::*;
use did_hosting_common::server::didcomm_unpack;
use serde_json::{Value, json};
use tracing::{info, warn};
use crate::auth::AuthClaims;
use crate::auth::session::now_epoch;
use crate::error::AppError;
use crate::messaging;
use crate::server::AppState;
const TRUST_PING_TYPE: &str = "https://didcomm.org/trust-ping/2.0/ping";
const DISCOVER_FEATURES_QUERY_TYPE: &str = "https://didcomm.org/discover-features/2.0/queries";
pub async fn handle(
auth: AuthClaims,
State(state): State<AppState>,
body: String,
) -> Result<Response, AppError> {
let (did_resolver, _secrets_resolver, _jwt_keys) = state.require_didcomm_auth()?;
let (msg, sender_base) = didcomm_unpack::unpack_signed(&body, did_resolver)
.await
.map_err(|e| AppError::Validation(format!("failed to unpack DIDComm message: {e}")))?;
if sender_base != auth.did {
return Err(AppError::Forbidden(
"DIDComm 'from' does not match authenticated DID".into(),
));
}
let server_did = state
.config
.server_did
.as_deref()
.ok_or_else(|| AppError::Internal("server_did not configured".into()))?;
if let Err(e) = state.replay_cache.check_and_insert(&auth.did, &msg.id) {
let code = e.didcomm_code();
let comment = e.user_message();
warn!(code, comment = %comment, msg_id = %msg.id, did = %auth.did, "DIDComm replay rejected");
let response_msg = Message::build(
uuid::Uuid::new_v4().to_string(),
MSG_PROBLEM_REPORT.to_string(),
json!({ "code": code, "comment": comment }),
)
.from(server_did.to_string())
.to(sender_base.to_string())
.thid(msg.id.clone())
.created_time(now_epoch())
.finalize();
let signing_key = state
.signing_key_bytes
.as_ref()
.ok_or_else(|| AppError::Internal("server signing key not configured".into()))?;
let kid = state
.identity
.as_ref()
.map(|i| i.current().signing_kid)
.unwrap_or_else(|| format!("{server_did}#key-0"));
let packed = pack::pack_signed(&response_msg, &kid, signing_key)
.map_err(|e| AppError::Internal(format!("failed to pack DIDComm response: {e}")))?;
return Ok((
StatusCode::OK,
[("content-type", "application/didcomm-signed+json")],
packed,
)
.into_response());
}
let (response_type, response_body) = match dispatch(&auth, &state, &msg, server_did).await {
Ok(result) => result,
Err(err) => {
let code = err.didcomm_code();
let comment = err.user_message();
warn!(
code,
comment = %comment,
msg_type = %msg.typ,
did = %auth.did,
"DIDComm protocol error"
);
(
MSG_PROBLEM_REPORT.to_string(),
json!({ "code": code, "comment": comment }),
)
}
};
let response_msg = Message::build(
uuid::Uuid::new_v4().to_string(),
response_type,
response_body,
)
.from(server_did.to_string())
.to(sender_base.to_string())
.thid(msg.id.clone())
.created_time(now_epoch())
.finalize();
let signing_key = state
.signing_key_bytes
.as_ref()
.ok_or_else(|| AppError::Internal("server signing key not configured".into()))?;
let kid = state
.identity
.as_ref()
.map(|i| i.current().signing_kid)
.unwrap_or_else(|| format!("{server_did}#key-0"));
let packed = pack::pack_signed(&response_msg, &kid, signing_key)
.map_err(|e| AppError::Internal(format!("failed to pack DIDComm response: {e}")))?;
Ok((
StatusCode::OK,
[("content-type", "application/didcomm-signed+json")],
packed,
)
.into_response())
}
async fn dispatch(
auth: &AuthClaims,
state: &AppState,
msg: &Message,
server_did: &str,
) -> Result<(String, Value), AppError> {
match msg.typ.as_str() {
TRUST_PING_TYPE => handle_trust_ping(msg, server_did),
DISCOVER_FEATURES_QUERY_TYPE => handle_discover_features(msg, server_did),
_ => messaging::dispatch_did_op(auth, state, msg).await,
}
}
fn handle_trust_ping(ping: &Message, server_did: &str) -> Result<(String, Value), AppError> {
let sender_did = ping
.from
.as_deref()
.ok_or_else(|| AppError::Validation("trust-ping has no 'from' DID".into()))?;
info!(from = sender_did, "received trust-ping");
let pong = TrustPing::default()
.generate_pong_message(ping, Some(server_did))
.map_err(|e| AppError::Internal(format!("trust-ping pong generation failed: {e}")))?;
Ok((pong.typ.clone(), pong.body.clone()))
}
fn handle_discover_features(
query_msg: &Message,
server_did: &str,
) -> Result<(String, Value), AppError> {
let sender_did = query_msg
.from
.as_deref()
.ok_or_else(|| AppError::Validation("discover-features query has no 'from' DID".into()))?;
info!(from = sender_did, "received discover-features query");
let features = DiscoverFeatures {
protocols: vec![
"https://didcomm.org/trust-ping/2.0".into(),
"https://didcomm.org/discover-features/2.0".into(),
"https://affinidi.com/webvh/1.0".into(),
],
goal_codes: vec![],
headers: vec![],
};
let disclosure = features
.generate_disclosure_message(server_did, sender_did, query_msg, None)
.map_err(|e| {
AppError::Internal(format!(
"discover-features disclosure generation failed: {e}"
))
})?;
Ok((disclosure.typ.clone(), disclosure.body.clone()))
}