headhunter_bindings/
authentication.rs1use std::borrow::Cow;
2use std::time::Duration;
3use thirtyfour::{By, DesiredCapabilities, WebDriver};
4use url::Url;
5
6use super::{Error, Result, request::*, response::*};
7
8pub struct ApplicationCredentials<'a> {
10 pub client_id: &'a str,
11 pub client_secret: &'a str,
12}
13
14pub struct UserCredentials<'a> {
16 pub login: &'a str,
17 pub password: &'a str,
18}
19
20pub struct AuthenticationClient;
23
24impl AuthenticationClient {
25 const SERVER_URL: &'static str = "http://localhost:9515";
26 const BASE_URL: &'static str = "https://hh.ru";
27
28 #[allow(clippy::new_without_default)]
30 pub fn new() -> Self {
31 Self
32 }
33
34 pub async fn get_authorization_code(
36 &self,
37 application: &ApplicationCredentials<'_>,
38 user: &UserCredentials<'_>,
39 ) -> Result<String> {
40 let driver = WebDriver::new(Self::SERVER_URL, DesiredCapabilities::chrome()).await?;
41
42 let request = UserAuthorizationRequest {
43 response_type: "code",
44 client_id: application.client_id,
45 };
46
47 let mut url = Url::parse(Self::BASE_URL)?;
48 url.set_path(UserAuthorizationRequest::method().ok_or_else(|| Error::UrlBuild)?);
49
50 let query = serde_urlencoded::to_string(request)?;
51 url.set_query(Some(&query));
52
53 driver.goto(url.as_str()).await?;
54
55 let elem_form = driver
56 .find(By::XPath("//*[@data-qa='account-login-form']"))
57 .await?;
58
59 let elem_expand_login_by_password = elem_form
60 .find(By::XPath("//*[@data-qa='expand-login-by_password']"))
61 .await?;
62
63 elem_expand_login_by_password.click().await?;
64
65 let elem_login = elem_form
66 .find(By::XPath("//*[@data-qa='login-input-username']"))
67 .await?;
68 let elem_password = elem_form
69 .find(By::XPath("//*[@data-qa='login-input-password']"))
70 .await?;
71
72 let elem_button = elem_form.find(By::Css("button[type='submit']")).await?;
73
74 elem_login.send_keys(user.login).await?;
75 elem_password.send_keys(user.password).await?;
76
77 elem_button.click().await?;
78
79 tokio::time::sleep(Duration::from_secs(3)).await;
80
81 let url = driver.current_url().await?;
82 let code = url
83 .query_pairs()
84 .find(|(key, _)| key == &Cow::Borrowed("code"))
85 .map(|(_, value)| value)
86 .expect("could not find authorization_code")
87 .to_string();
88
89 driver.quit().await?;
90
91 Ok(code)
92 }
93
94 async fn request<Req: Request>(&self, req: &Req) -> Result<Req::Response> {
95 let mut url = Url::parse(Self::BASE_URL)?;
96 url.set_path(Req::method().ok_or_else(|| Error::UrlBuild)?);
97
98 let client = reqwest::Client::new();
99 let response = client
100 .post(url)
101 .form(&req)
102 .send()
103 .await?
104 .json::<Req::Response>()
105 .await?;
106
107 Ok(response)
108 }
109
110 pub async fn perform_authentication(
112 &self,
113 application: &ApplicationCredentials<'_>,
114 authorization_code: &str,
115 ) -> Result<UserOpenAuthorizationResponse> {
116 self.request(&UserOpenAuthorizationRequest {
117 grant_type: "authorization_code",
118 client_id: application.client_id,
119 client_secret: application.client_secret,
120 code: authorization_code,
121 })
122 .await
123 }
124
125 pub async fn refresh_token(
127 &self,
128 refresh_token: &str,
129 ) -> Result<UserOpenAuthorizationResponse> {
130 self.request(&UserRenewOpenAuthorizationRequest {
131 grant_type: "refresh_token",
132 refresh_token,
133 })
134 .await
135 }
136}