1mod errors;
2use reqwest::{
3 header::{HeaderValue, CONTENT_TYPE},
4 Body, ClientBuilder, Method, Request, StatusCode, Url,
5};
6use serde::{Deserialize, Serialize};
7
8pub use errors::AuthenticationError;
9
10const AUTH_URL: &str = "https://iam.cloud.ibm.com/identity/token";
11#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12#[serde(rename_all = "camelCase")]
13pub(crate) struct TokenResponse {
14 #[serde(rename = "access_token")]
15 access_token: String,
16 #[serde(rename = "refresh_token")]
17 refresh_token: String,
18 #[serde(rename = "delegated_refresh_token")]
19 delegated_refresh_token: Option<String>,
20 #[serde(rename = "token_type")]
21 token_type: String,
22 #[serde(rename = "expires_in")]
23 expires_in: i64,
24 expiration: i64,
25 scope: Option<String>,
26}
27
28#[allow(dead_code)]
29impl TokenResponse {
30 pub fn access_token(&self) -> &str {
31 &self.access_token
32 }
33
34 pub fn refresh_token(&self) -> &str {
35 &self.refresh_token
36 }
37
38 pub fn token_type(&self) -> &str {
39 &self.token_type
40 }
41
42 pub fn expires_in(&self) -> i64 {
43 self.expires_in
44 }
45
46 pub fn expiration(&self) -> i64 {
47 self.expiration
48 }
49
50 pub fn scope(&self) -> Option<&String> {
51 self.scope.as_ref()
52 }
53
54 pub fn delegated_refresh_token(&self) -> Option<&String> {
55 self.delegated_refresh_token.as_ref()
56 }
57}
58
59#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
60pub struct IamAuthenticator {
62 access_token: TokenResponse,
63}
64
65impl IamAuthenticator {
66 pub async fn new(api_key: impl AsRef<str>) -> Result<Self, AuthenticationError> {
81 let url = Url::parse(AUTH_URL).unwrap();
82 let mut req = Request::new(Method::POST, url);
83 let headers = req.headers_mut();
84 let _ = headers.insert(
85 CONTENT_TYPE,
86 HeaderValue::from_str("application/x-www-form-urlencoded").unwrap(),
87 );
88 let body = req.body_mut();
89 *body = Some(Body::from(format!(
90 "grant_type=urn:ibm:params:oauth:grant-type:apikey&apikey={}",
91 api_key.as_ref()
92 )));
93 let client = ClientBuilder::new();
94
95 let client = client.build().unwrap();
96 let resp = client
97 .execute(req)
98 .await
99 .map_err(|e| AuthenticationError::ConnectionError(e.to_string()))?;
100 match resp.status() {
101 StatusCode::OK => {
102 let access_token: TokenResponse = resp.json().await.unwrap();
104 Ok(Self { access_token })
105 }
106 StatusCode::BAD_REQUEST => Err(AuthenticationError::ParameterValidationFailed),
107 _ => unreachable!(),
108 }
109 }
110
111 pub(crate) fn token_response(&self) -> &TokenResponse {
112 &self.access_token
113 }
114}