1use crate::{
2 config::Configuration,
3 error::{self, Error},
4 middleware::ErrorForStatus,
5 totp,
6};
7use serde::{Deserialize, Serialize};
8use std::time::Duration;
9use surf::Client as SurfClient;
10
11#[derive(Debug, Deserialize, Serialize)]
12pub struct Credentials {
13 pub username: String,
14 pub password: String,
15 pub totp_secret: String,
16}
17
18#[derive(Debug, Deserialize, Serialize)]
19#[serde(rename_all = "camelCase")]
20pub struct TotpBody {
21 totp_code: String,
22 method: String,
23}
24
25#[derive(Debug, Deserialize, Serialize)]
26#[serde(rename_all = "camelCase")]
27pub struct TotpResponse {
28 authentication_session: String,
29 push_subscription_id: String,
30 customer_id: String,
31 registration_complete: bool,
32}
33
34#[derive(Debug, Deserialize)]
35#[serde(rename_all = "camelCase")]
36pub struct SessionUser {
37 pub security_token: String,
38}
39
40#[derive(Debug, Deserialize)]
41#[serde(rename_all = "camelCase")]
42pub struct TwoFactorLogin {
43 pub transaction_id: String,
44 pub method: String,
45}
46
47#[derive(Debug, Deserialize)]
48#[serde(rename_all = "camelCase")]
49pub struct UserCredentialsResponse {
50 pub two_factor_login: TwoFactorLogin,
51}
52
53#[derive(Debug, Deserialize, Serialize)]
54#[serde(rename_all = "camelCase")]
55pub struct UserCredentialsRequest {
56 pub username: String,
57 pub password: String,
58}
59
60pub struct Client {
61 pub(crate) http_client: SurfClient,
62 pub(crate) config: Configuration,
63}
64
65impl Client {
66 pub async fn authenticate(credentials: &Credentials) -> Result<Client, error::Error> {
67 let config = Configuration::default();
69
70 let user_credentials_response = surf::post(config.urls.authenticate.as_str())
72 .middleware(ErrorForStatus)
73 .body_json(&UserCredentialsRequest {
74 username: credentials.username.clone(),
75 password: credentials.password.clone()
76 })?
77 .recv_json::<UserCredentialsResponse>()
78 .await?;
79
80 let totp_code = totp::generate_totp(&credentials.totp_secret);
82
83 let mut totp_response = surf::post(config.urls.totp.as_str())
85 .middleware(ErrorForStatus)
86 .header(
87 "cookie",
88 format!(
89 "AZAMFATRANSACTION={}",
90 user_credentials_response.two_factor_login.transaction_id
91 ),
92 )
93 .body_json(&TotpBody {
94 method: String::from("TOTP"),
95 totp_code: totp_code.to_string(),
96 })?
97 .await?;
98
99 let totp_data = totp_response.body_json::<TotpResponse>().await?;
101
102 let authentication_token = totp_data.authentication_session.clone();
103
104 let security_token = totp_response
106 .header("x-securitytoken")
107 .ok_or(Error::SecurityTokenError(String::from(
108 "Header 'x-securitytoken was missing in totp request'.",
109 )))?
110 .as_str();
111
112 let http_client =
114 Self::create_http_client(String::from(security_token), authentication_token.clone())?;
115
116 let client = Client {
118 http_client,
119 config: config.clone(),
120 };
121 return Ok(client);
122 }
123
124 fn create_http_client(
125 security_token: String,
126 authentication_token: String,
127 ) -> Result<SurfClient, Error> {
128 let client: SurfClient = surf::Config::new()
129 .set_timeout(Some(Duration::from_secs(5)))
130 .add_header("X-Securitytoken", security_token)?
131 .add_header("X-AuthenticationSession", authentication_token)?
132 .try_into()
133 .map_err(|_| {
134 Error::CreateHTTPClientError(String::from("Could not create http client"))
135 })?;
136 let client = client.with(ErrorForStatus);
137
138 return Ok(client);
139 }
140}