fix(auth): refresh tokens before authenticated requests
Move token refresh checks into the shared Rust connection/API path so long-running authenticated operations stop reusing stale access tokens. This covers recursive download and upload traversal, recursive ls via the shared APIs, and direct authenticated commands such as cp, mv, rm, and chacl. Also surface HTTP failures earlier in the affected API methods instead of failing later during response parsing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
+105
-11
@@ -1,5 +1,6 @@
|
||||
use reqwest::Client;
|
||||
use reqwest::header::{ACCEPT, AUTHORIZATION, HeaderMap, HeaderValue, USER_AGENT};
|
||||
use reqwest::{Client, Response};
|
||||
use serde::Serialize;
|
||||
|
||||
fn build_user_agent() -> String {
|
||||
let info = os_info::get();
|
||||
@@ -26,8 +27,10 @@ fn build_user_agent() -> String {
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
/// HTTP transport layer for MDRS API calls.
|
||||
pub struct MDRSConnection {
|
||||
pub remote: Option<String>,
|
||||
pub url: String,
|
||||
pub client: Client,
|
||||
pub token: Option<String>,
|
||||
@@ -36,12 +39,18 @@ pub struct MDRSConnection {
|
||||
impl MDRSConnection {
|
||||
pub fn new(url: &str) -> Self {
|
||||
MDRSConnection {
|
||||
remote: None,
|
||||
url: url.to_string(),
|
||||
client: Client::new(),
|
||||
token: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_remote(mut self, remote: &str) -> Self {
|
||||
self.remote = Some(remote.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Create a new connection that shares the HTTP client (and its connection
|
||||
/// pool) with the receiver but uses a fresh access token. Useful for
|
||||
/// spawning per-task connections without allocating a new connection pool
|
||||
@@ -51,12 +60,23 @@ impl MDRSConnection {
|
||||
/// keeps the shared pool intact.
|
||||
pub fn with_token(&self, access_token: String) -> Self {
|
||||
MDRSConnection {
|
||||
remote: self.remote.clone(),
|
||||
url: self.url.clone(),
|
||||
client: self.client.clone(),
|
||||
token: Some(access_token),
|
||||
}
|
||||
}
|
||||
|
||||
async fn connection_with_fresh_token(&self) -> Result<Self, anyhow::Error> {
|
||||
match (&self.remote, &self.token) {
|
||||
(Some(remote), Some(_)) => {
|
||||
let cache = crate::cache::load_cache_with_token_refresh(remote).await?;
|
||||
Ok(self.with_token(cache.token.access))
|
||||
}
|
||||
_ => Ok(self.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_url(&self, path: &str) -> String {
|
||||
format!("{}/{}", self.url.trim_end_matches('/'), path)
|
||||
}
|
||||
@@ -79,24 +99,98 @@ impl MDRSConnection {
|
||||
headers
|
||||
}
|
||||
|
||||
pub async fn get(&self, path: &str) -> reqwest::Result<reqwest::Response> {
|
||||
self.client
|
||||
.get(self.build_url(path))
|
||||
.headers(self.prepare_headers())
|
||||
pub async fn get(&self, path: &str) -> Result<Response, anyhow::Error> {
|
||||
let conn = self.connection_with_fresh_token().await?;
|
||||
Ok(conn
|
||||
.client
|
||||
.get(conn.build_url(path))
|
||||
.headers(conn.prepare_headers())
|
||||
.send()
|
||||
.await
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn get_with_query<Q>(&self, path: &str, query: &Q) -> Result<Response, anyhow::Error>
|
||||
where
|
||||
Q: Serialize + ?Sized,
|
||||
{
|
||||
let conn = self.connection_with_fresh_token().await?;
|
||||
Ok(conn
|
||||
.client
|
||||
.get(conn.build_url(path))
|
||||
.headers(conn.prepare_headers())
|
||||
.query(query)
|
||||
.send()
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn get_url(&self, url: &str) -> Result<Response, anyhow::Error> {
|
||||
let conn = self.connection_with_fresh_token().await?;
|
||||
Ok(conn
|
||||
.client
|
||||
.get(if url.starts_with("http") {
|
||||
url.to_string()
|
||||
} else {
|
||||
conn.build_url(url)
|
||||
})
|
||||
.headers(conn.prepare_headers())
|
||||
.send()
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn post_json<B>(&self, path: &str, body: &B) -> Result<Response, anyhow::Error>
|
||||
where
|
||||
B: Serialize + ?Sized,
|
||||
{
|
||||
let conn = self.connection_with_fresh_token().await?;
|
||||
Ok(conn
|
||||
.client
|
||||
.post(conn.build_url(path))
|
||||
.headers(conn.prepare_headers())
|
||||
.json(body)
|
||||
.send()
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn post_multipart(
|
||||
&self,
|
||||
path: &str,
|
||||
form: reqwest::multipart::Form,
|
||||
) -> reqwest::Result<reqwest::Response> {
|
||||
self.client
|
||||
.post(self.build_url(path))
|
||||
.headers(self.prepare_headers())
|
||||
) -> Result<Response, anyhow::Error> {
|
||||
let conn = self.connection_with_fresh_token().await?;
|
||||
Ok(conn
|
||||
.client
|
||||
.post(conn.build_url(path))
|
||||
.headers(conn.prepare_headers())
|
||||
.multipart(form)
|
||||
.send()
|
||||
.await
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn delete(&self, path: &str) -> Result<Response, anyhow::Error> {
|
||||
let conn = self.connection_with_fresh_token().await?;
|
||||
Ok(conn
|
||||
.client
|
||||
.delete(conn.build_url(path))
|
||||
.headers(conn.prepare_headers())
|
||||
.send()
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn delete_with_query<Q>(
|
||||
&self,
|
||||
path: &str,
|
||||
query: &Q,
|
||||
) -> Result<Response, anyhow::Error>
|
||||
where
|
||||
Q: Serialize + ?Sized,
|
||||
{
|
||||
let conn = self.connection_with_fresh_token().await?;
|
||||
Ok(conn
|
||||
.client
|
||||
.delete(conn.build_url(path))
|
||||
.headers(conn.prepare_headers())
|
||||
.query(query)
|
||||
.send()
|
||||
.await?)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user