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