tuwunel_api/client/keys/
upload_signatures.rs1use axum::extract::State;
2use futures::StreamExt;
3use ruma::{UserId, api::client::keys::upload_signatures};
4use serde_json::value::RawValue;
5use tuwunel_core::{Result, debug, debug_warn, utils::IterStream};
6
7use crate::Ruma;
8
9pub(crate) async fn upload_signatures_route(
13 State(services): State<crate::State>,
14 body: Ruma<upload_signatures::v3::Request>,
15) -> Result<upload_signatures::v3::Response> {
16 let sender_user = body.sender_user();
17
18 if body.signed_keys.is_empty() {
19 debug!("Empty signed_keys sent in key signature upload");
20 return Ok(upload_signatures::v3::Response::new());
21 }
22
23 body.signed_keys
24 .iter()
25 .flat_map(|(user_id, keys)| {
26 keys.iter().flat_map(move |(key_id, key)| {
27 signatures_from_key(sender_user, key_id, key)
28 .map(move |sig| (user_id.as_ref(), key_id, sig))
29 })
30 })
31 .stream()
32 .for_each_concurrent(None, async |(user_id, key_id, signature)| {
33 services
34 .users
35 .sign_key(user_id, key_id, signature, sender_user)
36 .await
37 .inspect_err(|e| debug_warn!("{e}"))
38 .ok();
39 })
40 .await;
41
42 Ok(upload_signatures::v3::Response::default())
43}
44
45fn signatures_from_key(
46 sender_user: &UserId,
47 key_id: &str,
48 key: &RawValue,
49) -> impl Iterator<Item = (String, String)> + Send + 'static {
50 serde_json::to_value(key)
51 .inspect_err(|e| debug_warn!(?key_id, "Invalid \"key\" JSON: {e}"))
52 .ok()
53 .and_then(|key| key.get("signatures").cloned())
54 .and_then(|sigs| sigs.get(sender_user.to_string()).cloned())
55 .and_then(|val| val.as_object().cloned())
56 .into_iter()
57 .flatten()
58 .filter_map(|(sig_id, val)| Some((sig_id, val.as_str().map(ToOwned::to_owned)?)))
59}