discovery_connect/api/auth.rs
1// Copyright 2023 Ikerian AG
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use reqwest::Client;
16use serde::{Deserialize, Serialize};
17use serde_json;
18
19#[derive(Debug, Serialize)]
20pub struct AuthPayload {
21 pub username: String,
22 pub password: String,
23 pub grant_type: &'static str,
24 pub mfa_code: Option<String>,
25}
26
27#[derive(Debug, Deserialize)]
28pub struct AuthResponse {
29 pub challenge: Option<String>,
30 pub access_token: String,
31 pub refresh_token: String,
32}
33
34/// Retrieves a new access token using a refresh token.
35///
36/// Sends a request to refresh an access token using the provided refresh token. This function constructs
37/// a refresh token payload, sends it to the specified OAuth token endpoint, and handles the server's response.
38///
39/// # Arguments
40///
41/// * `client` - A reference to a `Client` object used to send HTTP requests.
42/// * `url` - A `String` reference representing the base URL of the OAuth token endpoint.
43/// * `refresh_token` - A `String` reference representing the refresh token used to obtain a new access token.
44///
45/// # Returns
46///
47/// A `Result` containing either the authentication response data on success (`AuthResponse`),
48/// or an `reqwest::Error` on failure. The `AuthResponse` includes the new access token and other relevant information.
49///
50/// # Errors
51///
52/// Returns an `Err` of type `reqwest::Error` if the access token request fails, if the server response status
53/// is not OK, or if there is an issue with the server's response format.
54///
55/// # Example
56///
57/// ```
58/// use discovery_connect::auth::{access_token, AuthResponse};
59/// use reqwest::Client;
60///
61/// async fn get_access_token_example() {
62/// let client = Client::new();
63/// let base_url = "https://api.example.discovery.retinai.com";
64/// let refresh_token = "your_refresh_token";
65/// let response = access_token(&client, &base_url, &refresh_token).await;
66/// match response {
67/// Ok(auth_response) => println!("Access token: {}", auth_response.access_token),
68/// Err(e) => println!("Error: {}", e),
69/// }
70/// }
71/// ```
72pub async fn access_token(
73 client: &Client,
74 url: &str,
75 refresh_token: &str,
76) -> Result<AuthResponse, reqwest::Error> {
77 let refresh_payload = serde_json::json!({
78 "grant_type": "refresh_token",
79 "refresh_token": refresh_token
80 });
81
82 let url = format!("{}/oauth/token/", url);
83
84 match client.post(&url).json(&refresh_payload).send().await {
85 Ok(response) => {
86 if response.status() != reqwest::StatusCode::OK {
87 let e: reqwest::Error = response.error_for_status().unwrap_err();
88 return Err(e);
89 }
90 let response: AuthResponse = response.json().await.unwrap();
91 Ok(response)
92 }
93 Err(error) => {
94 eprintln!(" error: {:?}", error);
95 Err(error)
96 }
97 }
98}
99
100/// Authenticates a user against a server and obtains an authentication token.
101///
102/// Asynchronously authenticates a user using their credentials and optionally a multi-factor authentication (MFA) code.
103/// This function builds the authentication request, sends it to the server, and processes the response.
104///
105/// # Arguments
106///
107/// * `client` - A reference to a `reqwest::Client` used for making HTTP requests.
108/// * `url` - A string slice representing the base URL of the authentication server.
109/// * `client_id` - A string slice representing the client identifier.
110/// * `client_secret` - A string slice representing the client secret.
111/// * `email` - A string slice representing the user's email address.
112/// * `password` - A string slice representing the user's password.
113/// * `mfa_code` - An optional string slice representing the multi-factor authentication code.
114///
115/// # Returns
116///
117/// A `Result` containing either the authentication response data on success (`AuthResponse`),
118/// or an `reqwest::Error` on failure.
119///
120/// # Errors
121///
122/// Returns an `Err` of type `reqwest::Error` if the authentication request fails or if the server
123/// response status is not OK.
124///
125/// # Example
126///
127/// ```
128/// use discovery_connect::{QueryClient};
129/// use discovery_connect::auth::{login, AuthResponse};
130/// use std::sync::Arc;
131///
132/// let qc = Arc::new(QueryClient::new(
133/// "https://api.example.discovery.retinai.com",
134/// "client_id",
135/// "client_secret",
136/// "user@example",
137/// "password123",
138/// std::time::Duration::from_secs(10)));
139/// async {
140/// let res = login(
141/// &qc.client,
142/// "https://api.example.discovery.retinai.com",
143/// "client_id123",
144/// "client_secret456",
145/// "user@example.com",
146/// "password123",
147/// Some("123456")
148/// ).await;
149/// match res {
150/// Ok(auth_response) => println!("Access Token: {:?}", auth_response.access_token),
151/// Err(e) => println!("Error: {}", e),
152/// }
153/// };
154/// ```
155pub async fn login(
156 client: &Client,
157 url: &str,
158 client_id: &str,
159 client_secret: &str,
160 email: &str,
161 password: &str,
162 mfa_code: Option<&str>,
163) -> Result<AuthResponse, reqwest::Error> {
164 let credentials = format!("{}:{}", client_id, client_secret);
165 // let credentials = general_purpose::STANDARD.encode(credentials);
166 let credentials = base64_url::encode(&credentials);
167
168 let url = format!("{}/oauth/token/", url);
169
170 let payload = AuthPayload {
171 username: email.to_string(),
172 password: password.to_string(),
173 grant_type: "password",
174 mfa_code: mfa_code.map(|s| s.to_string()),
175 };
176
177 let c = client
178 .post(&url)
179 .header("Authorization", format!("Basic {}", credentials))
180 .json(&payload);
181
182 match c.send().await {
183 Ok(response) => {
184 if response.status() != reqwest::StatusCode::OK {
185 return Err(response.error_for_status().unwrap_err());
186 }
187 let response: AuthResponse = response.json().await.unwrap();
188 Ok(response)
189 }
190 Err(error) => Err(error),
191 }
192}