reverse_engineered_twitter_api/
auth.rs

1use super::{ReAPI, BEARER_TOKEN, GUEST_ACTIVE_URL, LOGIN_URL, VERIFY_CREDENTIALS_URL};
2use reqwest::{self, Error};
3use serde::Deserialize;
4use serde_json::{self, json};
5
6#[derive(Deserialize)]
7pub struct User {
8    pub id: i64,
9    pub id_str: String,
10    pub name: String,
11    pub screen_name: String,
12}
13#[derive(Deserialize)]
14pub struct OpenAccount {
15    pub user: Option<User>,
16    pub next_link: Option<Link>,
17    pub attribution_event: Option<String>,
18}
19
20#[derive(Deserialize)]
21pub struct Subtask {
22    pub subtask_id: String,
23    pub open_account: Option<OpenAccount>,
24}
25
26#[derive(Deserialize)]
27pub struct ApiError {
28    pub code: i64,
29    pub message: String,
30}
31
32#[derive(Deserialize)]
33pub struct Flow {
34    pub errors: Option<Vec<ApiError>>,
35    pub flow_token: String,
36    pub status: String,
37    pub subtasks: Vec<Subtask>,
38    pub js_instrumentation: Option<Insrumentation>,
39}
40
41#[derive(Deserialize)]
42pub struct Insrumentation {
43    pub url: String,
44    pub timeout_ms: i64,
45    pub next_link: Link,
46}
47
48#[derive(Deserialize)]
49pub struct Link {
50    pub link_type: String,
51    pub link_id: String,
52}
53
54#[derive(Deserialize)]
55pub struct GuestToken {
56    pub guest_token: String,
57}
58
59#[derive(Deserialize)]
60pub struct VerifyCredentials {
61    pub errors: Option<Vec<ApiError>>,
62}
63
64impl ReAPI {
65    pub fn new() -> ReAPI {
66        let client = reqwest::ClientBuilder::new()
67            .cookie_store(true)
68            .build()
69            .unwrap();
70        return ReAPI {
71            client,
72            csrf_token: String::from(""),
73            guest_token: String::from(""),
74        };
75    }
76    async fn get_flow(&mut self, body: serde_json::Value) -> Result<Flow, Error> {
77        if self.guest_token.is_empty() {
78            self.get_guest_token().await?
79        }
80        let res = self
81            .client
82            .post(LOGIN_URL)
83            .header("Authorization", format!("Bearer {}", BEARER_TOKEN))
84            .header("Content-Type", "application/json")
85            .header("User-Agent", "TwitterAndroid/99")
86            .header("X-Guest-Token", self.guest_token.replace("\"", ""))
87            .header("X-Twitter-Auth-Type", "OAuth2Client")
88            .header("X-Twitter-Active-User", "yes")
89            .header("X-Twitter-Client-Language", "en")
90            .json(&body)
91            .send()
92            .await?;
93
94        let cookies = res.cookies();
95        for cookie in cookies {
96            if cookie.name().eq("ct0") {
97                self.csrf_token = cookie.value().to_string()
98            }
99        }
100        let result: Flow = res.json().await?;
101        return Ok(result);
102    }
103
104    async fn get_flow_token(&mut self, data: serde_json::Value) -> Result<String, String> {
105        let res = self.get_flow(data);
106        match res.await {
107            Ok(info) => {
108                if info.subtasks.len() > 0 {
109                    let subtask_id = info.subtasks[0].subtask_id.as_str();
110                    match subtask_id {
111                        "LoginEnterAlternateIdentifierSubtask"
112                        | "LoginAcid"
113                        | "LoginTwoFactorAuthChallenge"
114                        | "DenyLoginSubtask" => {
115                            return Err(format!("Auth error: {}", subtask_id));
116                        }
117                        _ => return Ok(info.flow_token),
118                    }
119                }
120                return Ok(info.flow_token);
121            }
122            Err(e) => Err(format!("Request error: {}", e.to_string())),
123        }
124    }
125
126    async fn get_guest_token(&mut self) -> Result<(), Error> {
127        let token = format!("Bearer {}", BEARER_TOKEN);
128        let res = self
129            .client
130            .post(GUEST_ACTIVE_URL)
131            .header("Authorization", token)
132            .send()
133            .await;
134        match res {
135            Ok(r) => {
136                let op = r.json::<serde_json::Value>().await?;
137                let guest_token = op.get("guest_token").unwrap();
138                self.guest_token = guest_token.to_string();
139                return Ok(());
140            }
141            Err(e) => Err(e),
142        }
143    }
144
145    pub async fn login(
146        &mut self,
147        user_name: &str,
148        password: &str,
149        confirmation: &str,
150    ) -> Result<String, String> {
151        // flow start
152        let data = json!(
153            {
154                "flow_name": "login",
155                "input_flow_data": {
156                    "flow_context" : {
157                        "debug_overrides": {},
158                        "start_location": {
159                            "location": "splash_screen"
160                        }
161                    }
162                }
163            }
164        );
165        let flow_token = self.get_flow_token(data).await?;
166
167        // flow instrumentation step
168        let data = json!(
169            {
170                "flow_token": flow_token,
171                "subtask_inputs" : [{
172                    "subtask_id": "LoginJsInstrumentationSubtask",
173                    "js_instrumentation":{
174                        "response": "{}",
175                        "link": "next_link"
176                    }
177                }],
178            }
179        );
180        let flow_token = self.get_flow_token(data).await?;
181
182        // flow username step
183        let data = json!(
184            {
185                "flow_token": flow_token,
186                "subtask_inputs" : [{
187                    "subtask_id": "LoginEnterUserIdentifierSSO",
188                    "settings_list": {
189                        "setting_responses" : [{
190                            "key":           "user_identifier",
191                            "response_data": {
192                                "text_data" :{
193                                    "result": user_name
194                                }
195                            }
196                        }],
197                        "link": "next_link"
198                    }
199                }]
200            }
201        );
202        let flow_token = self.get_flow_token(data).await?;
203
204        // flow password step
205        let data = json!(
206            {
207                "flow_token": flow_token,
208                "subtask_inputs": [{
209                    "subtask_id":     "LoginEnterPassword",
210                    "enter_password": {
211                        "password": password,
212                        "link": "next_link"
213                    },
214                }]
215            }
216        );
217        let flow_token = self.get_flow_token(data).await?;
218
219        // flow duplication check
220        let data = json!(
221            {
222                "flow_token": flow_token,
223                "subtask_inputs": [{
224                    "subtask_id":              "AccountDuplicationCheck",
225                    "check_logged_in_account": {
226                        "link": "AccountDuplicationCheck_false"
227                    },
228                }]
229            }
230        );
231        let flow_token = self.get_flow_token(data).await;
232
233        match flow_token {
234            Err(e) => {
235                let mut confirmation_subtask = "";
236                for item in vec!["LoginAcid", "LoginTwoFactorAuthChallenge"] {
237                    if e.contains(item) {
238                        confirmation_subtask = item;
239                        break;
240                    }
241                }
242                if !confirmation_subtask.is_empty() {
243                    if confirmation.is_empty() {
244                        let msg = format!(
245                            "confirmation data required for {}",
246                            confirmation_subtask.to_owned()
247                        );
248                        return Err(msg);
249                    }
250                    let data = json!(
251                        {
252                            "flow_token": "",
253                            "subtask_inputs": {
254                                    "subtask_id": confirmation_subtask,
255                                    "enter_text": {
256                                        "text": confirmation,
257                                        "link": "next_link",
258                                    },
259                            },
260                        }
261                    );
262                    return self.get_flow_token(data).await;
263                }
264                Ok("".to_owned())
265            }
266            Ok(_) => return Ok("".to_owned()),
267        }
268    }
269
270    pub async fn is_logged_in(&mut self) -> bool {
271        let req = self
272            .client
273            .get(VERIFY_CREDENTIALS_URL)
274            .header("Authorization", format!("Bearer {}", BEARER_TOKEN))
275            .header("X-CSRF-Token", self.csrf_token.to_owned())
276            .build()
277            .unwrap();
278        let res = self.client.execute(req).await.unwrap();
279        let cookies = res.cookies();
280        for cookie in cookies {
281            if cookie.name().eq("ct0") {
282                self.csrf_token = cookie.value().to_string()
283            }
284        }
285        let text = res.text().await.unwrap();
286        let res: VerifyCredentials = serde_json::from_str(&text).unwrap();
287        res.errors.is_none()
288    }
289}