Skip to main content

libsignal_service/websocket/
account.rs

1use chrono::{DateTime, Utc};
2use reqwest::Method;
3use serde::{Deserialize, Serialize};
4use uuid::Uuid;
5
6use crate::{
7    content::ServiceError,
8    proto::DeviceName,
9    utils::{
10        serde_device_id, serde_e164, serde_optional_base64,
11        serde_optional_base64_url_safe_no_pad, serde_optional_prost_base64,
12    },
13    websocket,
14};
15
16use super::SignalWebSocket;
17
18#[derive(Debug, Serialize, Deserialize)]
19#[serde(rename_all = "camelCase")]
20pub struct DeviceId {
21    #[serde(with = "serde_device_id")]
22    pub device_id: libsignal_core::DeviceId,
23}
24
25#[derive(Debug, Serialize, Deserialize)]
26#[serde(rename_all = "camelCase")]
27pub struct DeviceInfo {
28    #[serde(with = "serde_device_id")]
29    pub id: libsignal_core::DeviceId,
30    pub registration_id: i32,
31    pub name: Option<String>,
32    #[serde(with = "chrono::serde::ts_milliseconds")]
33    pub created_at: DateTime<Utc>,
34    #[serde(with = "chrono::serde::ts_milliseconds")]
35    pub last_seen: DateTime<Utc>,
36}
37
38#[derive(Debug, Serialize, Deserialize)]
39#[serde(rename_all = "camelCase")]
40pub(crate) struct DeviceInfoEncrypted {
41    #[serde(with = "serde_device_id")]
42    pub id: libsignal_core::DeviceId,
43    pub name: Option<String>,
44    pub registration_id: i32,
45    pub created_at_ciphertext: String,
46    #[serde(with = "chrono::serde::ts_milliseconds")]
47    pub last_seen: DateTime<Utc>,
48}
49
50#[derive(Debug, Serialize, Deserialize)]
51#[serde(rename_all = "camelCase")]
52/// kept in sync with https://github.com/signalapp/Signal-Server/blob/main/service/src/main/java/org/whispersystems/textsecuregcm/entities/AccountAttributes.java#L25
53pub struct AccountAttributes {
54    pub registration_id: u32,
55    pub voice: bool,
56    pub video: bool,
57    pub fetches_messages: bool,
58    #[serde(default, skip_serializing_if = "Option::is_none")]
59    pub registration_lock: Option<String>,
60    #[serde(default, with = "serde_optional_base64")]
61    pub unidentified_access_key: Option<Vec<u8>>,
62    pub unrestricted_unidentified_access: bool,
63    pub discoverable_by_phone_number: bool,
64    #[serde(default, skip_serializing_if = "Option::is_none")]
65    pub capabilities: Option<DeviceCapabilities>,
66    #[serde(default, with = "serde_optional_prost_base64")]
67    pub name: Option<DeviceName>,
68    pub pni_registration_id: u32,
69    #[serde(
70        default,
71        with = "serde_optional_base64",
72        skip_serializing_if = "Option::is_none"
73    )]
74    pub recovery_password: Option<Vec<u8>>,
75}
76
77// Keep in sync with https://github.com/signalapp/Signal-Server/blob/main/service/src/main/java/org/whispersystems/textsecuregcm/storage/DeviceCapability.java.
78// When updating this, also consider updating LinkCapabilities.
79#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
80#[serde(rename_all = "camelCase")]
81pub struct DeviceCapabilities {
82    #[serde(default)]
83    pub storage: bool,
84    #[serde(default)]
85    pub transfer: bool,
86    #[serde(default)]
87    pub attachment_backfill: bool,
88    #[serde(default)]
89    pub spqr: bool,
90    // For some reason, this uses snake case while everything else uses camel case.
91    #[serde(default, rename = "profiles_v2")]
92    pub profiles_v2: bool,
93    #[serde(default)]
94    pub username_change_sync_message: bool,
95}
96
97impl Default for DeviceCapabilities {
98    fn default() -> Self {
99        DeviceCapabilities {
100            storage: false,
101            transfer: false,
102            attachment_backfill: false,
103            spqr: true,
104            profiles_v2: false,
105            username_change_sync_message: false,
106        }
107    }
108}
109
110#[cfg(test)]
111mod test {
112    #[test]
113    fn device_capabilities_serialization_weird_casing() {
114        let capabilities = super::DeviceCapabilities::default();
115        let json = serde_json::to_string(&capabilities)
116            .expect("Serialize capabilities");
117        assert!(json.contains("usernameChangeSyncMessage"));
118        assert!(json.contains("profiles_v2"));
119    }
120}
121
122#[derive(Debug, Deserialize)]
123#[serde(rename_all = "camelCase")]
124pub struct WhoAmIResponse {
125    #[serde(rename = "uuid")]
126    pub aci: Uuid,
127    #[serde(default)] // nil when not present (yet)
128    pub pni: Uuid,
129    #[serde(with = "serde_e164")]
130    pub number: libsignal_core::E164,
131    /// Hash of the account's username, if one is set.
132    #[serde(default, with = "serde_optional_base64_url_safe_no_pad")]
133    pub username_hash: Option<Vec<u8>>,
134    /// Handle (UUID) of the account's username link, if one is set.
135    ///
136    /// Decrypting the username also requires the link entropy, which the
137    /// server never sees; only the full `signal.me` link carries it.
138    #[serde(default)]
139    pub username_link_handle: Option<Uuid>,
140}
141
142impl SignalWebSocket<websocket::Identified> {
143    /// Method used to check our own UUID
144    pub async fn whoami(&mut self) -> Result<WhoAmIResponse, ServiceError> {
145        self.http_request(Method::GET, "/v1/accounts/whoami")?
146            .send()
147            .await?
148            .service_error_for_status()
149            .await?
150            .json()
151            .await
152    }
153
154    /// Fetches a list of all devices tied to the authenticated account.
155    ///
156    /// This list include the device that sends the request.
157    pub(crate) async fn devices(
158        &mut self,
159    ) -> Result<Vec<DeviceInfoEncrypted>, ServiceError> {
160        #[derive(serde::Deserialize)]
161        struct DeviceInfoList {
162            devices: Vec<DeviceInfoEncrypted>,
163        }
164
165        let devices: DeviceInfoList = self
166            .http_request(Method::GET, "/v1/devices")?
167            .send()
168            .await?
169            .service_error_for_status()
170            .await?
171            .json()
172            .await?;
173
174        Ok(devices.devices)
175    }
176
177    pub async fn set_account_attributes(
178        &mut self,
179        attributes: AccountAttributes,
180    ) -> Result<(), ServiceError> {
181        self.http_request(Method::PUT, "/v1/accounts/attributes")?
182            .send_json(&attributes)
183            .await?
184            .service_error_for_status()
185            .await?;
186
187        Ok(())
188    }
189
190    /// Unregister and delete the account from Signal servers.
191    ///
192    /// This permanently deletes the account and all associated data (groups, contacts, messages).
193    /// After calling this, the phone number can be re-registered with a fresh account.
194    ///
195    /// CAUTION: This is irreversible. All account data will be lost.
196    pub async fn unregister_account(&mut self) -> Result<(), ServiceError> {
197        self.http_request(Method::DELETE, "/v1/accounts/me")?
198            .send()
199            .await?
200            .service_error_for_status()
201            .await?;
202
203        Ok(())
204    }
205}