inth_oauth2_async/client/
mod.rs1mod error;
4pub use error::ClientError;
5
6pub mod http_client;
7pub use http_client::HttpClient;
8
9pub mod response;
10
11use serde_json::{self, Value};
12use url::form_urlencoded::Serializer;
13use url::Url;
14
15use crate::client::response::FromResponse;
16use crate::error::OAuth2Error;
17use crate::provider::Provider;
18use crate::token::{Lifetime, Refresh, Token};
19
20#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct Client<P> {
23 pub provider: P,
25
26 pub client_id: String,
28
29 pub client_secret: String,
31
32 pub redirect_uri: Option<String>,
34}
35
36impl<P: Provider> Client<P> {
37 pub fn new(
53 provider: P,
54 client_id: String,
55 client_secret: String,
56 redirect_uri: Option<String>,
57 ) -> Self {
58 Client {
59 provider,
60 client_id,
61 client_secret,
62 redirect_uri,
63 }
64 }
65
66 pub fn auth_uri(&self, scope: Option<&str>, state: Option<&str>) -> Url
89 {
90 let mut uri = self.provider.auth_uri().clone();
91
92 {
93 let mut query = uri.query_pairs_mut();
94
95 query.append_pair("response_type", "code");
96 query.append_pair("client_id", &self.client_id);
97
98 if let Some(ref redirect_uri) = self.redirect_uri {
99 query.append_pair("redirect_uri", redirect_uri);
100 }
101 if let Some(scope) = scope {
102 query.append_pair("scope", scope);
103 }
104 if let Some(state) = state {
105 query.append_pair("state", state);
106 }
107 }
108
109 uri
110 }
111
112 async fn post_token(
113 &self,
114 http_client: &impl HttpClient,
115 body: String,
116 ) -> Result<Value, ClientError> {
117 let body = {
118 let mut body = Serializer::new(body);
120 if self.provider.credentials_in_body() {
121 body.append_pair("client_id", &self.client_id);
122 body.append_pair("client_secret", &self.client_secret);
123 }
124 body.finish()
125 };
126
127 let json = http_client
128 .post(
129 self.provider.token_uri().as_str(),
130 &self.client_id,
131 &self.client_secret,
132 body,
133 )
134 .await?;
135
136 let error = OAuth2Error::from_response(&json);
137
138 if let Ok(error) = error {
139 Err(ClientError::from(error))
140 } else {
141 Ok(json)
142 }
143 }
144
145 pub async fn request_token(
149 &self,
150 http_client: &impl HttpClient,
151 code: &str,
152 ) -> Result<P::Token, ClientError> {
153 let body = {
154 let mut body = Serializer::new(String::new());
156 body.append_pair("grant_type", "authorization_code");
157 body.append_pair("code", code);
158
159 if let Some(ref redirect_uri) = self.redirect_uri {
160 body.append_pair("redirect_uri", redirect_uri);
161 }
162
163 body.finish()
164 };
165
166 let json = self.post_token(http_client, body).await?;
167 let token = P::Token::from_response(&json)?;
168 Ok(token)
169 }
170}
171
172impl<P> Client<P> where P: Provider, P::Token: Token<Refresh> {
173 pub async fn refresh_token(
177 &self,
178 http_client: &impl HttpClient,
179 token: P::Token,
180 scope: Option<&str>,
181 ) -> Result<P::Token, ClientError> {
182 let body = {
183 let mut body = Serializer::new(String::new());
185 body.append_pair("grant_type", "refresh_token");
186 body.append_pair("refresh_token", token.lifetime().refresh_token());
187
188 if let Some(scope) = scope {
189 body.append_pair("scope", scope);
190 }
191
192 body.finish()
193 };
194
195 let json = self.post_token(http_client, body).await?;
196 let token = P::Token::from_response_inherit(&json, &token)?;
197 Ok(token)
198 }
199
200 pub async fn ensure_token(
202 &self,
203 http_client: &impl HttpClient,
204 token: P::Token,
205 ) -> Result<P::Token, ClientError> {
206 if token.lifetime().expired() {
207 self.refresh_token(http_client, token, None).await
208 } else {
209 Ok(token)
210 }
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217 use crate::token::{Bearer, Static};
218
219 struct Test {
220 auth_uri: Url,
221 token_uri: Url
222 }
223 impl Provider for Test {
224 type Lifetime = Static;
225 type Token = Bearer<Static>;
226 fn auth_uri(&self) -> &Url { &self.auth_uri }
227 fn token_uri(&self) -> &Url { &self.token_uri }
228 }
229 impl Test {
230 fn new() -> Self {
231 Test {
232 auth_uri: Url::parse("http://example.com/oauth2/auth").unwrap(),
233 token_uri: Url::parse("http://example.com/oauth2/token").unwrap()
234 }
235 }
236 }
237
238 #[test]
239 fn auth_uri() {
240 let client = Client::new(Test::new(), String::from("foo"), String::from("bar"), None);
241 assert_eq!(
242 "http://example.com/oauth2/auth?response_type=code&client_id=foo",
243 client.auth_uri(None, None).as_str()
244 );
245 }
246
247 #[test]
248 fn auth_uri_with_redirect_uri() {
249 let client = Client::new(
250 Test::new(),
251 String::from("foo"),
252 String::from("bar"),
253 Some(String::from("http://example.com/oauth2/callback")),
254 );
255 assert_eq!(
256 "http://example.com/oauth2/auth?response_type=code&client_id=foo&redirect_uri=http%3A%2F%2Fexample.com%2Foauth2%2Fcallback",
257 client.auth_uri(None, None).as_str()
258 );
259 }
260
261 #[test]
262 fn auth_uri_with_scope() {
263 let client = Client::new(Test::new(), String::from("foo"), String::from("bar"), None);
264 assert_eq!(
265 "http://example.com/oauth2/auth?response_type=code&client_id=foo&scope=baz",
266 client.auth_uri(Some("baz"), None).as_str()
267 );
268 }
269
270 #[test]
271 fn auth_uri_with_state() {
272 let client = Client::new(Test::new(), String::from("foo"), String::from("bar"), None);
273 assert_eq!(
274 "http://example.com/oauth2/auth?response_type=code&client_id=foo&state=baz",
275 client.auth_uri(None, Some("baz")).as_str()
276 );
277 }
278}