mangadex_api/v5/oauth/
login.rs1use derive_builder::Builder;
37use mangadex_api_schema::v5::oauth::OAuthTokenResponse;
38use mangadex_api_schema::v5::AuthTokens;
39use mangadex_api_types::oauth::GrantTypeSupported;
40use reqwest::Method;
41use serde::Serialize;
42#[cfg(not(test))]
43use url::Url;
44
45use crate::v5::HttpClientRef;
46use mangadex_api_types::error::Result;
47use mangadex_api_types::{Password, Username};
48
49#[cfg_attr(
53 feature = "deserializable-endpoint",
54 derive(serde::Deserialize, getset::Getters, getset::Setters)
55)]
56#[derive(Debug, Clone, Builder)]
57#[builder(
58 setter(into, strip_option),
59 build_fn(error = "mangadex_api_types::error::BuilderError")
60)]
61pub struct RetriveTokens {
62 #[doc(hidden)]
64 #[cfg_attr(feature = "deserializable-endpoint", serde(skip))]
65 #[builder(pattern = "immutable")]
66 #[cfg_attr(feature = "deserializable-endpoint", getset(set = "pub", get = "pub"))]
67 pub http_client: HttpClientRef,
68
69 pub username: Username,
70
71 pub password: Password,
72}
73
74#[derive(Clone, Serialize)]
75struct RetriveTokenBody {
76 grant_type: GrantTypeSupported,
77 username: Username,
78 password: Password,
79 client_id: String,
80 client_secret: String,
81}
82
83impl RetriveTokens {
84 pub async fn send(&mut self) -> Result<OAuthTokenResponse> {
85 let res = {
86 let client = {
87 #[cfg(all(
88 not(feature = "multi-thread"),
89 not(feature = "tokio-multi-thread"),
90 not(feature = "rw-multi-thread")
91 ))]
92 {
93 &self.http_client.try_borrow()?
94 }
95 #[cfg(any(feature = "multi-thread", feature = "tokio-multi-thread"))]
96 {
97 &self.http_client.lock().await
98 }
99 #[cfg(feature = "rw-multi-thread")]
100 {
101 &self.http_client.read().await
102 }
103 };
104 let client_info = client
105 .get_client_info()
106 .ok_or(mangadex_api_types::error::Error::MissingClientInfo)?;
107 let params = RetriveTokenBody {
108 grant_type: GrantTypeSupported::Password,
109 username: self.username.to_owned(),
110 password: self.password.to_owned(),
111 client_id: client_info.client_id.to_owned(),
112 client_secret: client_info.client_secret.to_owned(),
113 };
114 #[cfg(test)]
115 let res = client
116 .client
117 .request(
118 Method::POST,
119 client
120 .base_url
121 .join("/realms/mangadex/protocol/openid-connect/token")?,
122 )
123 .form(¶ms)
124 .send()
125 .await?;
126 #[cfg(not(test))]
127 let res = client
128 .client
129 .request(
130 Method::POST,
131 Url::parse(crate::AUTH_URL)?
132 .join("/realms/mangadex/protocol/openid-connect/token")?,
133 )
134 .form(¶ms)
135 .send()
136 .await?;
137 res.json::<OAuthTokenResponse>().await?
138 };
139 {
140 let auth_tokens: AuthTokens = From::from(res.clone());
141 let client = {
142 #[cfg(all(
143 not(feature = "multi-thread"),
144 not(feature = "tokio-multi-thread"),
145 not(feature = "rw-multi-thread")
146 ))]
147 {
148 &mut self.http_client.try_borrow_mut()?
149 }
150 #[cfg(any(feature = "multi-thread", feature = "tokio-multi-thread"))]
151 {
152 &mut self.http_client.lock().await
153 }
154 #[cfg(feature = "rw-multi-thread")]
155 {
156 &mut self.http_client.write().await
157 }
158 };
159 client.set_auth_tokens(&auth_tokens);
160 };
161 Ok(res)
162 }
163}
164
165builder_send! {
166 #[builder] RetriveTokensBuilder,
167 OAuthTokenResponse
168}
169
170#[cfg(test)]
171mod tests {
172 use mangadex_api_schema::v5::oauth::ClientInfo;
173 use mangadex_api_types::oauth::GrantTypeSupported;
174 use serde_json::json;
175 use url::Url;
176 use wiremock::matchers::{body_string, header, method, path};
177 use wiremock::{Mock, MockServer, ResponseTemplate};
178
179 use crate::v5::oauth::login::RetriveTokenBody;
180 use crate::v5::AuthTokens;
181 use crate::{HttpClient, MangaDexClient};
182 use mangadex_api_types::{Password, Username};
183 use serde_urlencoded::to_string;
184
185 #[tokio::test]
186 async fn login_fires_a_request_to_base_url() -> anyhow::Result<()> {
187 let mock_server = MockServer::start().await;
188 let http_client: HttpClient = HttpClient::builder()
189 .base_url(Url::parse(&mock_server.uri())?)
190 .build()?;
191 let mut mangadex_client = MangaDexClient::new_with_http_client(http_client);
192
193 let client_info: ClientInfo = ClientInfo {
194 client_id: "someClientId".to_string(),
195 client_secret: "someClientSecret".to_string(),
196 };
197
198 mangadex_client.set_client_info(&client_info).await?;
199
200 let username = Username::parse("myusername")?;
201
202 let password = Password::parse("mypassword")?;
203
204 let auth_tokens = AuthTokens {
205 session: "sessiontoken".to_string(),
206 refresh: "refreshtoken".to_string(),
207 };
208
209 let response_body = json!({
210 "access_token": auth_tokens.session.clone(),
211 "expires_in": 900,
212 "refresh_expires_in": 2414162,
213 "refresh_token": auth_tokens.refresh.clone(),
214 "token_type": "Bearer",
215 "not-before-policy": 0,
216 "session_state": "c176499d-6e8d-4ddf-ad59-6d922be66431",
217 "scope": "groups email profile",
218 "client_type": "personal"
219 });
220 let expected_body: String = to_string(RetriveTokenBody {
221 grant_type: GrantTypeSupported::Password,
222 username: username.clone(),
223 password: password.clone(),
224 client_id: client_info.client_id.clone(),
225 client_secret: client_info.client_secret.clone(),
226 })?;
227
228 Mock::given(method("POST"))
229 .and(path(r"/realms/mangadex/protocol/openid-connect/token"))
230 .and(header("Content-Type", "application/x-www-form-urlencoded"))
231 .and(body_string(expected_body))
232 .respond_with(ResponseTemplate::new(200).set_body_json(response_body))
233 .expect(1)
234 .mount(&mock_server)
235 .await;
236
237 let _ = mangadex_client
238 .oauth()
239 .login()
240 .username(username.clone())
241 .password(password.clone())
242 .send()
243 .await?;
244
245 #[cfg(all(
246 not(feature = "multi-thread"),
247 not(feature = "tokio-multi-thread"),
248 not(feature = "rw-multi-thread")
249 ))]
250 assert_eq!(
251 mangadex_client.http_client.try_borrow()?.get_tokens(),
252 Some(&auth_tokens)
253 );
254 #[cfg(any(feature = "multi-thread", feature = "tokio-multi-thread"))]
255 assert_eq!(
256 mangadex_client.http_client.lock().await.get_tokens(),
257 Some(&auth_tokens)
258 );
259 #[cfg(feature = "rw-multi-thread")]
260 assert_eq!(
261 mangadex_client.http_client.read().await.get_tokens(),
262 Some(&auth_tokens)
263 );
264
265 Ok(())
266 }
267}