libsignal_service/push_service/
profile.rs1use libsignal_protocol::Aci;
2use reqwest::Method;
3use serde::{Deserialize, Serialize};
4use zkgroup::profiles::{ProfileKeyCommitment, ProfileKeyVersion};
5
6use crate::{
7 configuration::Endpoint,
8 content::ServiceError,
9 push_service::AvatarWrite,
10 utils::{serde_base64, serde_optional_base64},
11};
12
13use super::{DeviceCapabilities, HttpAuthOverride, PushService, ReqwestExt};
14
15#[derive(Debug, Deserialize)]
16#[serde(rename_all = "camelCase")]
17pub struct SignalServiceProfile {
18 #[serde(default, with = "serde_optional_base64")]
19 pub identity_key: Option<Vec<u8>>,
20 #[serde(default, with = "serde_optional_base64")]
21 pub name: Option<Vec<u8>>,
22 #[serde(default, with = "serde_optional_base64")]
23 pub about: Option<Vec<u8>>,
24 #[serde(default, with = "serde_optional_base64")]
25 pub about_emoji: Option<Vec<u8>>,
26
27 pub avatar: Option<String>,
31 pub unidentified_access: Option<String>,
32
33 #[serde(default)]
34 pub unrestricted_unidentified_access: bool,
35
36 pub capabilities: DeviceCapabilities,
37}
38
39#[derive(Debug, Serialize)]
40#[serde(rename_all = "camelCase")]
41struct SignalServiceProfileWrite<'s> {
42 version: &'s str,
44 #[serde(with = "serde_base64")]
45 name: &'s [u8],
46 #[serde(with = "serde_base64")]
47 about: &'s [u8],
48 #[serde(with = "serde_base64")]
49 about_emoji: &'s [u8],
50 avatar: bool,
51 same_avatar: bool,
52 #[serde(with = "serde_base64")]
53 commitment: &'s [u8],
54}
55
56impl PushService {
57 pub async fn retrieve_profile_by_id(
58 &mut self,
59 address: Aci,
60 profile_key: Option<zkgroup::profiles::ProfileKey>,
61 ) -> Result<SignalServiceProfile, ServiceError> {
62 let path = if let Some(key) = profile_key {
63 let version =
64 bincode::serialize(&key.get_profile_key_version(address))?;
65 let version = std::str::from_utf8(&version)
66 .expect("hex encoded profile key version");
67 format!("/v1/profile/{}/{}", address.service_id_string(), version)
68 } else {
69 format!("/v1/profile/{}", address.service_id_string())
70 };
71 self.request(
73 Method::GET,
74 Endpoint::service(path),
75 HttpAuthOverride::NoOverride,
76 )?
77 .send()
78 .await?
79 .service_error_for_status()
80 .await?
81 .json()
82 .await
83 .map_err(Into::into)
84 }
85
86 pub async fn retrieve_profile_avatar(
87 &mut self,
88 path: &str,
89 ) -> Result<impl futures::io::AsyncRead + Send + Unpin, ServiceError> {
90 self.get_from_cdn(0, path).await
91 }
92
93 pub async fn retrieve_groups_v2_profile_avatar(
94 &mut self,
95 path: &str,
96 ) -> Result<impl futures::io::AsyncRead + Send + Unpin, ServiceError> {
97 self.get_from_cdn(0, path).await
98 }
99
100 pub async fn write_profile<'s, C, S>(
107 &mut self,
108 version: &ProfileKeyVersion,
109 name: &[u8],
110 about: &[u8],
111 emoji: &[u8],
112 commitment: &ProfileKeyCommitment,
113 avatar: AvatarWrite<&mut C>,
114 ) -> Result<Option<String>, ServiceError>
115 where
116 C: std::io::Read + Send + 's,
117 S: AsRef<str>,
118 {
119 let version = bincode::serialize(version)?;
121 let version = std::str::from_utf8(&version)
122 .expect("profile_key_version is hex encoded string");
123 let commitment = bincode::serialize(commitment)?;
124
125 let command = SignalServiceProfileWrite {
126 version,
127 name,
128 about,
129 about_emoji: emoji,
130 avatar: !matches!(avatar, AvatarWrite::NoAvatar),
131 same_avatar: matches!(avatar, AvatarWrite::RetainAvatar),
132 commitment: &commitment,
133 };
134
135 let upload_url: Result<String, _> = self
137 .request(
138 Method::PUT,
139 Endpoint::service("/v1/profile"),
140 HttpAuthOverride::NoOverride,
141 )?
142 .json(&command)
143 .send()
144 .await?
145 .service_error_for_status()
146 .await?
147 .json()
148 .await;
149
150 match (upload_url, avatar) {
151 (_url, AvatarWrite::NewAvatar(_avatar)) => {
152 unreachable!("Uploading avatar unimplemented");
154 },
155 (Err(_), AvatarWrite::RetainAvatar)
159 | (Err(_), AvatarWrite::NoAvatar) => {
160 Ok(None)
162 },
163 (Ok(_resp), AvatarWrite::RetainAvatar)
164 | (Ok(_resp), AvatarWrite::NoAvatar) => {
165 tracing::warn!(
166 "No avatar supplied but got avatar upload URL. Ignoring"
167 );
168 Ok(None)
169 },
170 }
171 }
172}