client: actually do authentication properly
This commit is contained in:
@@ -3,7 +3,7 @@ extern crate serde;
|
||||
|
||||
use std::{path::PathBuf, str};
|
||||
|
||||
use crate::api::TokenStore;
|
||||
use crate::api::AuthenticationStore;
|
||||
use hyper::{Body, Client, Method, Request, Uri};
|
||||
|
||||
use async_trait::async_trait;
|
||||
@@ -16,10 +16,9 @@ use crate::{
|
||||
|
||||
type HttpClient = Client<hyper::client::HttpConnector>;
|
||||
|
||||
pub struct HTTPAPIClient<K: TokenStore + Send + Sync> {
|
||||
pub struct HTTPAPIClient<K: AuthenticationStore + Send + Sync> {
|
||||
pub base_url: Uri,
|
||||
pub token_store: K,
|
||||
credentials: Option<Credentials>,
|
||||
pub auth_store: K,
|
||||
client: HttpClient,
|
||||
}
|
||||
|
||||
@@ -92,7 +91,7 @@ impl<B> AuthSetting for hyper::http::Request<B> {
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<K: TokenStore + Send + Sync> APIInterface for HTTPAPIClient<K> {
|
||||
impl<K: AuthenticationStore + Send + Sync> APIInterface for HTTPAPIClient<K> {
|
||||
type Error = Error;
|
||||
|
||||
async fn get_version(&mut self) -> Result<String, Self::Error> {
|
||||
@@ -111,10 +110,15 @@ impl<K: TokenStore + Send + Sync> APIInterface for HTTPAPIClient<K> {
|
||||
jwt: String,
|
||||
}
|
||||
|
||||
log::debug!("Authenticating with username: {:?}", credentials.username);
|
||||
|
||||
let body = || -> Body { serde_json::to_string(&credentials).unwrap().into() };
|
||||
let token: AuthResponse = self.request_with_body_retry("authenticate", Method::POST, body, false).await?;
|
||||
let token = JwtToken::new(&token.jwt).map_err(|_| Error::DecodeError)?;
|
||||
self.token_store.set_token(token.clone()).await;
|
||||
|
||||
log::debug!("Saving token: {:?}", token);
|
||||
self.auth_store.set_token(token.clone()).await;
|
||||
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
@@ -144,12 +148,11 @@ impl<K: TokenStore + Send + Sync> APIInterface for HTTPAPIClient<K> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: TokenStore + Send + Sync> HTTPAPIClient<K> {
|
||||
pub fn new(base_url: Uri, credentials: Option<Credentials>, token_store: K) -> HTTPAPIClient<K> {
|
||||
impl<K: AuthenticationStore + Send + Sync> HTTPAPIClient<K> {
|
||||
pub fn new(base_url: Uri, auth_store: K) -> HTTPAPIClient<K> {
|
||||
HTTPAPIClient {
|
||||
base_url,
|
||||
credentials,
|
||||
token_store,
|
||||
auth_store,
|
||||
client: Client::new(),
|
||||
}
|
||||
}
|
||||
@@ -198,7 +201,7 @@ impl<K: TokenStore + Send + Sync> HTTPAPIClient<K> {
|
||||
.expect("Unable to build request")
|
||||
};
|
||||
|
||||
let token = self.token_store.get_token().await;
|
||||
let token = self.auth_store.get_token().await;
|
||||
let request = build_request(&token);
|
||||
let mut response = self.client.request(request).await?;
|
||||
|
||||
@@ -213,11 +216,14 @@ impl<K: TokenStore + Send + Sync> HTTPAPIClient<K> {
|
||||
return Err(Error::ClientError("Unauthorized".into()));
|
||||
}
|
||||
|
||||
if let Some(credentials) = &self.credentials {
|
||||
self.authenticate(credentials.clone()).await?;
|
||||
if let Some(credentials) = &self.auth_store.get_credentials().await {
|
||||
log::debug!("Renewing token using credentials: u: {:?}", credentials.username);
|
||||
let new_token = self.authenticate(credentials.clone()).await?;
|
||||
|
||||
let request = build_request(&token);
|
||||
let request = build_request(&Some(new_token));
|
||||
response = self.client.request(request).await?;
|
||||
} else {
|
||||
return Err(Error::ClientError("Unauthorized, no credentials provided".into()));
|
||||
}
|
||||
},
|
||||
|
||||
@@ -233,6 +239,9 @@ impl<K: TokenStore + Send + Sync> HTTPAPIClient<K> {
|
||||
let parsed: T = match serde_json::from_slice(&body) {
|
||||
Ok(result) => Ok(result),
|
||||
Err(json_err) => {
|
||||
log::error!("Error deserializing JSON: {:?}", json_err);
|
||||
log::error!("Body: {:?}", String::from_utf8_lossy(&body));
|
||||
|
||||
// If JSON deserialization fails, try to interpret it as plain text
|
||||
// Unfortunately the server does return things like this...
|
||||
let s = str::from_utf8(&body).map_err(|_| Error::DecodeError)?;
|
||||
@@ -247,17 +256,17 @@ impl<K: TokenStore + Send + Sync> HTTPAPIClient<K> {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::api::InMemoryTokenStore;
|
||||
use crate::api::InMemoryAuthenticationStore;
|
||||
|
||||
#[cfg(test)]
|
||||
fn local_mock_client() -> HTTPAPIClient<InMemoryTokenStore> {
|
||||
fn local_mock_client() -> HTTPAPIClient<InMemoryAuthenticationStore> {
|
||||
let base_url = "http://localhost:5738".parse().unwrap();
|
||||
let credentials = Credentials {
|
||||
let credentials = Credentials {
|
||||
username: "test".to_string(),
|
||||
password: "test".to_string(),
|
||||
};
|
||||
|
||||
HTTPAPIClient::new(base_url, credentials.into(), InMemoryTokenStore::new())
|
||||
HTTPAPIClient::new(base_url, InMemoryAuthenticationStore::new(Some(credentials)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -33,29 +33,38 @@ pub trait APIInterface {
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait TokenStore {
|
||||
pub trait AuthenticationStore {
|
||||
async fn get_credentials(&mut self) -> Option<Credentials>;
|
||||
async fn get_token(&mut self) -> Option<JwtToken>;
|
||||
async fn set_token(&mut self, token: JwtToken);
|
||||
}
|
||||
|
||||
pub struct InMemoryTokenStore {
|
||||
pub struct InMemoryAuthenticationStore {
|
||||
credentials: Option<Credentials>,
|
||||
token: Option<JwtToken>,
|
||||
}
|
||||
|
||||
impl Default for InMemoryTokenStore {
|
||||
impl Default for InMemoryAuthenticationStore {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
Self::new(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl InMemoryTokenStore {
|
||||
pub fn new() -> Self {
|
||||
Self { token: None }
|
||||
impl InMemoryAuthenticationStore {
|
||||
pub fn new(credentials: Option<Credentials>) -> Self {
|
||||
Self {
|
||||
credentials,
|
||||
token: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl TokenStore for InMemoryTokenStore {
|
||||
impl AuthenticationStore for InMemoryAuthenticationStore {
|
||||
async fn get_credentials(&mut self) -> Option<Credentials> {
|
||||
self.credentials.clone()
|
||||
}
|
||||
|
||||
async fn get_token(&mut self) -> Option<JwtToken> {
|
||||
self.token.clone()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user