diff --git a/Cargo.lock b/Cargo.lock index 7b619fd..24f8823 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -857,6 +857,7 @@ name = "kordophoned" version = "0.1.0" dependencies = [ "anyhow", + "async-trait", "dbus", "dbus-codegen", "dbus-crossroads", diff --git a/kordophone-db/src/database.rs b/kordophone-db/src/database.rs index 3841140..29a8723 100644 --- a/kordophone-db/src/database.rs +++ b/kordophone-db/src/database.rs @@ -8,7 +8,7 @@ pub use tokio::sync::Mutex; use crate::repository::Repository; use crate::settings::Settings; -pub use kordophone::api::TokenManagement; +pub use kordophone::api::TokenStore; use kordophone::model::JwtToken; use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; @@ -89,7 +89,8 @@ impl DatabaseAccess for Arc> { static TOKEN_KEY: &str = "token"; -impl TokenManagement for Database { +#[async_trait] +impl TokenStore for Database { async fn get_token(&mut self) -> Option { self.with_settings(|settings| { let token: Result> = settings.get(TOKEN_KEY); diff --git a/kordophone/src/api/http_client.rs b/kordophone/src/api/http_client.rs index 024b44d..46bf197 100644 --- a/kordophone/src/api/http_client.rs +++ b/kordophone/src/api/http_client.rs @@ -3,6 +3,7 @@ extern crate serde; use std::{path::PathBuf, str}; +use crate::api::{TokenStore, InMemoryTokenStore}; use hyper::{Body, Client, Method, Request, Uri}; use async_trait::async_trait; @@ -15,10 +16,10 @@ use crate::{ type HttpClient = Client; -pub struct HTTPAPIClient { +pub struct HTTPAPIClient { pub base_url: Uri, + pub token_store: K, credentials: Option, - auth_token: Option, client: HttpClient, } @@ -91,7 +92,7 @@ impl AuthSetting for hyper::http::Request { } #[async_trait] -impl APIInterface for HTTPAPIClient { +impl APIInterface for HTTPAPIClient { type Error = Error; async fn get_version(&mut self) -> Result { @@ -113,7 +114,7 @@ impl APIInterface for HTTPAPIClient { 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.auth_token = Some(token.clone()); + self.token_store.set_token(token.clone()).await; Ok(token) } @@ -124,12 +125,12 @@ impl APIInterface for HTTPAPIClient { } } -impl HTTPAPIClient { - pub fn new(base_url: Uri, credentials: Option) -> HTTPAPIClient { +impl HTTPAPIClient { + pub fn new(base_url: Uri, credentials: Option, token_store: K) -> HTTPAPIClient { HTTPAPIClient { base_url, credentials, - auth_token: Option::None, + token_store, client: Client::new(), } } @@ -178,7 +179,8 @@ impl HTTPAPIClient { .expect("Unable to build request") }; - let request = build_request(&self.auth_token); + let token = self.token_store.get_token().await; + let request = build_request(&token); let mut response = self.client.request(request).await?; log::debug!("-> Response: {:}", response.status()); @@ -195,7 +197,7 @@ impl HTTPAPIClient { if let Some(credentials) = &self.credentials { self.authenticate(credentials.clone()).await?; - let request = build_request(&self.auth_token); + let request = build_request(&token); response = self.client.request(request).await?; } }, @@ -228,14 +230,14 @@ mod test { use super::*; #[cfg(test)] - fn local_mock_client() -> HTTPAPIClient { + fn local_mock_client() -> HTTPAPIClient { let base_url = "http://localhost:5738".parse().unwrap(); let credentials = Credentials { username: "test".to_string(), password: "test".to_string(), }; - HTTPAPIClient::new(base_url, credentials.into()) + HTTPAPIClient::new(base_url, credentials.into(), InMemoryTokenStore::new()) } #[cfg(test)] diff --git a/kordophone/src/api/mod.rs b/kordophone/src/api/mod.rs index 55ea9db..de7a8cd 100644 --- a/kordophone/src/api/mod.rs +++ b/kordophone/src/api/mod.rs @@ -26,22 +26,24 @@ pub trait APIInterface { async fn authenticate(&mut self, credentials: Credentials) -> Result; } -pub trait TokenManagement { +#[async_trait] +pub trait TokenStore { async fn get_token(&mut self) -> Option; async fn set_token(&mut self, token: JwtToken); } -pub struct InMemoryTokenManagement { +pub struct InMemoryTokenStore { token: Option, } -impl InMemoryTokenManagement { +impl InMemoryTokenStore { pub fn new() -> Self { Self { token: None } } } -impl TokenManagement for InMemoryTokenManagement { +#[async_trait] +impl TokenStore for InMemoryTokenStore { async fn get_token(&mut self) -> Option { self.token.clone() } diff --git a/kordophoned/Cargo.toml b/kordophoned/Cargo.toml index e14a322..74c2556 100644 --- a/kordophoned/Cargo.toml +++ b/kordophoned/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] anyhow = "1.0.98" +async-trait = "0.1.88" dbus = "0.9.7" dbus-crossroads = "0.5.2" dbus-tokio = "0.7.6" diff --git a/kordophoned/src/daemon/mod.rs b/kordophoned/src/daemon/mod.rs index 6b10f8a..d2d140a 100644 --- a/kordophoned/src/daemon/mod.rs +++ b/kordophoned/src/daemon/mod.rs @@ -12,7 +12,7 @@ use thiserror::Error; use tokio::sync::mpsc::{Sender, Receiver}; use std::sync::Arc; use tokio::sync::Mutex; -use futures_util::FutureExt; +use async_trait::async_trait; use kordophone_db::{ database::{Database, DatabaseAccess}, @@ -24,7 +24,7 @@ use kordophone::model::JwtToken; use kordophone::api::{ http_client::{Credentials, HTTPAPIClient}, APIInterface, - TokenManagement, + TokenStore, }; #[derive(Debug, Error)] @@ -35,6 +35,21 @@ pub enum DaemonError { pub type DaemonResult = Result>; +struct DatabaseTokenStore { + database: Arc>, +} + +#[async_trait] +impl TokenStore for DatabaseTokenStore { + async fn get_token(&mut self) -> Option { + self.database.lock().await.get_token().await + } + + async fn set_token(&mut self, token: JwtToken) { + self.database.lock().await.set_token(token).await; + } +} + pub struct Daemon { pub event_sender: Sender, event_receiver: Receiver, @@ -122,7 +137,8 @@ impl Daemon { } ), _ => None, - } + }, + DatabaseTokenStore { database: database.clone() } ); // This function needed to implement TokenManagement @@ -169,7 +185,7 @@ impl Daemon { Ok(settings) } - async fn get_client(&mut self) -> Result { + async fn get_client(&mut self) -> Result> { let settings = self.database.with_settings(|s| Settings::from_db(s) ).await?; @@ -188,7 +204,8 @@ impl Daemon { } ), _ => None, - } + }, + DatabaseTokenStore { database: self.database.clone() } ); Ok(client) diff --git a/kordophoned/src/daemon/settings.rs b/kordophoned/src/daemon/settings.rs index 4205d3b..29f9899 100644 --- a/kordophoned/src/daemon/settings.rs +++ b/kordophoned/src/daemon/settings.rs @@ -7,6 +7,7 @@ mod keys { pub static CREDENTIAL_ITEM: &str = "CredentialItem"; } +#[derive(Debug)] pub struct Settings { pub server_url: Option, pub username: Option, diff --git a/kpcli/src/client/mod.rs b/kpcli/src/client/mod.rs index d5c441e..a0c146e 100644 --- a/kpcli/src/client/mod.rs +++ b/kpcli/src/client/mod.rs @@ -1,14 +1,14 @@ use kordophone::APIInterface; use kordophone::api::http_client::HTTPAPIClient; use kordophone::api::http_client::Credentials; -use kordophone::api::InMemoryTokenManagement; +use kordophone::api::InMemoryTokenStore; use dotenv; use anyhow::Result; use clap::Subcommand; use crate::printers::{ConversationPrinter, MessagePrinter}; -pub fn make_api_client_from_env() -> HTTPAPIClient { +pub fn make_api_client_from_env() -> HTTPAPIClient { dotenv::dotenv().ok(); // read from env @@ -23,7 +23,7 @@ pub fn make_api_client_from_env() -> HTTPAPIClient { .expect("KORDOPHONE_PASSWORD must be set"), }; - HTTPAPIClient::new(base_url.parse().unwrap(), credentials.into()) + HTTPAPIClient::new(base_url.parse().unwrap(), credentials.into(), InMemoryTokenStore::new()) } #[derive(Subcommand)] @@ -52,7 +52,7 @@ impl Commands { } struct ClientCli { - api: HTTPAPIClient, + api: HTTPAPIClient, } impl ClientCli {