libsignal_service/push_service/
registration.rs1use derivative::Derivative;
2use libsignal_protocol::IdentityKey;
3use reqwest::Method;
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7use super::{AccountAttributes, PushService, ServiceError};
8use crate::{
9 configuration::Endpoint,
10 pre_keys::{KyberPreKeyEntity, SignedPreKeyEntity},
11 push_service::{response::ReqwestExt, HttpAuthOverride},
12 utils::serde_base64,
13};
14
15#[derive(Derivative, Clone, Serialize, Deserialize)]
18#[derivative(Debug)]
19pub struct AuthCredentials {
20 pub username: String,
21 #[derivative(Debug = "ignore")]
22 pub password: String,
23}
24
25#[derive(Debug, Deserialize)]
26#[serde(rename_all = "camelCase")]
27pub struct RegistrationLockFailure {
28 pub length: Option<u32>,
29 pub time_remaining: Option<u64>,
30 #[serde(rename = "backup_credentials")]
31 pub svr1_credentials: Option<AuthCredentials>,
32 pub svr2_credentials: Option<AuthCredentials>,
33}
34
35#[derive(Debug, Deserialize)]
36#[serde(rename_all = "camelCase")]
37pub struct VerifyAccountResponse {
38 #[serde(rename = "uuid")]
39 pub aci: Uuid,
40 pub pni: Uuid,
41 pub storage_capable: bool,
42 #[serde(default)]
43 pub number: Option<String>,
44}
45
46#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize)]
47#[serde(rename_all = "snake_case")]
48pub enum VerificationTransport {
49 Sms,
50 Voice,
51}
52
53#[derive(Clone, Debug)]
54pub enum RegistrationMethod<'a> {
55 SessionId(&'a str),
56 RecoveryPassword(&'a str),
57}
58
59impl<'a> RegistrationMethod<'a> {
60 pub fn session_id(&'a self) -> Option<&'a str> {
61 match self {
62 Self::SessionId(x) => Some(x),
63 _ => None,
64 }
65 }
66
67 pub fn recovery_password(&'a self) -> Option<&'a str> {
68 match self {
69 Self::RecoveryPassword(x) => Some(x),
70 _ => None,
71 }
72 }
73}
74
75#[derive(Debug, Serialize)]
76#[serde(rename_all = "camelCase")]
77pub struct DeviceActivationRequest {
78 pub aci_signed_pre_key: SignedPreKeyEntity,
79 pub pni_signed_pre_key: SignedPreKeyEntity,
80 pub aci_pq_last_resort_pre_key: KyberPreKeyEntity,
81 pub pni_pq_last_resort_pre_key: KyberPreKeyEntity,
82}
83
84#[derive(Debug, Serialize)]
85pub struct CaptchaAttributes<'a> {
86 #[serde(rename = "type")]
87 pub challenge_type: &'a str,
88 pub token: &'a str,
89 pub captcha: &'a str,
90}
91
92#[derive(Debug, Clone, Deserialize)]
93#[serde(rename_all = "camelCase")]
94pub struct RegistrationSessionMetadataResponse {
95 pub id: String,
96 #[serde(default)]
97 pub next_sms: Option<i32>,
98 #[serde(default)]
99 pub next_call: Option<i32>,
100 #[serde(default)]
101 pub next_verification_attempt: Option<i32>,
102 pub allowed_to_request_code: bool,
103 #[serde(default)]
104 pub requested_information: Vec<String>,
105 pub verified: bool,
106}
107
108impl RegistrationSessionMetadataResponse {
109 pub fn push_challenge_required(&self) -> bool {
110 self.requested_information
112 .iter()
113 .any(|x| x.as_str() == "pushChallenge")
114 }
115
116 pub fn captcha_required(&self) -> bool {
117 self.requested_information
119 .iter()
120 .any(|x| x.as_str() == "captcha")
121 }
122}
123
124impl PushService {
125 pub async fn submit_registration_request(
126 &mut self,
127 registration_method: RegistrationMethod<'_>,
128 account_attributes: AccountAttributes,
129 skip_device_transfer: bool,
130 aci_identity_key: &IdentityKey,
131 pni_identity_key: &IdentityKey,
132 device_activation_request: DeviceActivationRequest,
133 ) -> Result<VerifyAccountResponse, ServiceError> {
134 #[derive(serde::Serialize, Debug)]
135 #[serde(rename_all = "camelCase")]
136 struct RegistrationSessionRequestBody<'a> {
137 session_id: Option<&'a str>,
144 recovery_password: Option<&'a str>,
145 account_attributes: AccountAttributes,
146 skip_device_transfer: bool,
147 every_signed_key_valid: bool,
148 #[serde(default, with = "serde_base64")]
149 pni_identity_key: Vec<u8>,
150 #[serde(default, with = "serde_base64")]
151 aci_identity_key: Vec<u8>,
152 #[serde(flatten)]
153 device_activation_request: DeviceActivationRequest,
154 }
155
156 self.request(
157 Method::POST,
158 Endpoint::service("/v1/registration"),
159 HttpAuthOverride::NoOverride,
160 )?
161 .json(&RegistrationSessionRequestBody {
162 session_id: registration_method.session_id(),
163 recovery_password: registration_method.recovery_password(),
164 account_attributes,
165 skip_device_transfer,
166 aci_identity_key: aci_identity_key.serialize().into(),
167 pni_identity_key: pni_identity_key.serialize().into(),
168 device_activation_request,
169 every_signed_key_valid: true,
170 })
171 .send()
172 .await?
173 .service_error_for_status()
174 .await?
175 .json()
176 .await
177 .map_err(Into::into)
178 }
179
180 pub async fn create_verification_session<'a>(
183 &mut self,
184 number: &'a str,
185 push_token: Option<&'a str>,
186 mcc: Option<&'a str>,
187 mnc: Option<&'a str>,
188 ) -> Result<RegistrationSessionMetadataResponse, ServiceError> {
189 #[derive(serde::Serialize, Debug)]
190 #[serde(rename_all = "camelCase")]
191 struct VerificationSessionMetadataRequestBody<'a> {
192 number: &'a str,
193 push_token: Option<&'a str>,
194 mcc: Option<&'a str>,
195 mnc: Option<&'a str>,
196 push_token_type: Option<&'a str>,
197 }
198
199 self.request(
200 Method::POST,
201 Endpoint::service("/v1/verification/session"),
202 HttpAuthOverride::Unidentified,
203 )?
204 .json(&VerificationSessionMetadataRequestBody {
205 number,
206 push_token_type: push_token.as_ref().map(|_| "fcm"),
207 push_token,
208 mcc,
209 mnc,
210 })
211 .send()
212 .await?
213 .service_error_for_status()
214 .await?
215 .json()
216 .await
217 .map_err(Into::into)
218 }
219
220 pub async fn patch_verification_session<'a>(
221 &mut self,
222 session_id: &'a str,
223 push_token: Option<&'a str>,
224 mcc: Option<&'a str>,
225 mnc: Option<&'a str>,
226 captcha: Option<&'a str>,
227 push_challenge: Option<&'a str>,
228 ) -> Result<RegistrationSessionMetadataResponse, ServiceError> {
229 #[derive(serde::Serialize, Debug)]
230 #[serde(rename_all = "camelCase")]
231 struct UpdateVerificationSessionRequestBody<'a> {
232 captcha: Option<&'a str>,
233 push_token: Option<&'a str>,
234 push_challenge: Option<&'a str>,
235 mcc: Option<&'a str>,
236 mnc: Option<&'a str>,
237 push_token_type: Option<&'a str>,
238 }
239
240 self.request(
241 Method::PATCH,
242 Endpoint::service(format!(
243 "/v1/verification/session/{}",
244 session_id
245 )),
246 HttpAuthOverride::Unidentified,
247 )?
248 .json(&UpdateVerificationSessionRequestBody {
249 captcha,
250 push_token_type: push_token.as_ref().map(|_| "fcm"),
251 push_token,
252 mcc,
253 mnc,
254 push_challenge,
255 })
256 .send()
257 .await?
258 .service_error_for_status()
259 .await?
260 .json()
261 .await
262 .map_err(Into::into)
263 }
264
265 pub async fn request_verification_code(
277 &mut self,
278 session_id: &str,
279 client: &str,
280 transport: VerificationTransport,
284 ) -> Result<RegistrationSessionMetadataResponse, ServiceError> {
285 #[derive(Debug, Serialize)]
286 struct VerificationCodeRequest<'a> {
287 transport: VerificationTransport,
288 client: &'a str,
289 }
290
291 self.request(
292 Method::POST,
293 Endpoint::service(format!(
294 "/v1/verification/session/{}/code",
295 session_id
296 )),
297 HttpAuthOverride::Unidentified,
298 )?
299 .json(&VerificationCodeRequest { transport, client })
300 .send()
301 .await?
302 .service_error_for_status()
303 .await?
304 .json()
305 .await
306 .map_err(Into::into)
307 }
308
309 pub async fn submit_verification_code(
310 &mut self,
311 session_id: &str,
312 verification_code: &str,
313 ) -> Result<RegistrationSessionMetadataResponse, ServiceError> {
314 #[derive(Debug, Serialize)]
315 struct VerificationCode<'a> {
316 code: &'a str,
317 }
318
319 self.request(
320 Method::PUT,
321 Endpoint::service(format!(
322 "/v1/verification/session/{}/code",
323 session_id
324 )),
325 HttpAuthOverride::Unidentified,
326 )?
327 .json(&VerificationCode {
328 code: verification_code,
329 })
330 .send()
331 .await?
332 .service_error_for_status()
333 .await?
334 .json()
335 .await
336 .map_err(Into::into)
337 }
338}