Private
Public Access
1
0

implements authentication/token retrieval/keyring

This commit is contained in:
2025-05-03 01:06:50 -07:00
parent 461c37bd20
commit 26d54f91d5
11 changed files with 234 additions and 99 deletions

128
Cargo.lock generated
View File

@@ -430,6 +430,19 @@ dependencies = [
"dbus", "dbus",
] ]
[[package]]
name = "dbus-secret-service"
version = "4.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b42a16374481d92aed73ae45b1f120207d8e71d24fb89f357fadbd8f946fd84b"
dependencies = [
"dbus",
"futures-util",
"num",
"once_cell",
"rand 0.8.5",
]
[[package]] [[package]]
name = "dbus-tokio" name = "dbus-tokio"
version = "0.7.6" version = "0.7.6"
@@ -976,6 +989,16 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "keyring"
version = "3.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1961983669d57bdfe6c0f3ef8e4c229b5ef751afcc7d87e4271d2f71f6ccfa8b"
dependencies = [
"dbus-secret-service",
"log",
]
[[package]] [[package]]
name = "kordophone" name = "kordophone"
version = "0.1.0" version = "0.1.0"
@@ -1031,6 +1054,7 @@ dependencies = [
"directories", "directories",
"env_logger", "env_logger",
"futures-util", "futures-util",
"keyring",
"kordophone", "kordophone",
"kordophone-db", "kordophone-db",
"log", "log",
@@ -1190,12 +1214,76 @@ dependencies = [
"tempfile", "tempfile",
] ]
[[package]]
name = "num"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
dependencies = [
"num-bigint",
"num-complex",
"num-integer",
"num-iter",
"num-rational",
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
dependencies = [
"num-integer",
"num-traits",
]
[[package]]
name = "num-complex"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
dependencies = [
"num-traits",
]
[[package]] [[package]]
name = "num-conv" name = "num-conv"
version = "0.1.0" version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
dependencies = [
"num-bigint",
"num-integer",
"num-traits",
]
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.19" version = "0.2.19"
@@ -1388,14 +1476,35 @@ version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha 0.3.1",
"rand_core 0.6.4",
]
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.9.1" version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
dependencies = [ dependencies = [
"rand_chacha", "rand_chacha 0.9.0",
"rand_core", "rand_core 0.9.3",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core 0.6.4",
] ]
[[package]] [[package]]
@@ -1405,7 +1514,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [ dependencies = [
"ppv-lite86", "ppv-lite86",
"rand_core", "rand_core 0.9.3",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom 0.2.14",
] ]
[[package]] [[package]]
@@ -1925,7 +2043,7 @@ dependencies = [
"http 1.3.1", "http 1.3.1",
"httparse", "httparse",
"log", "log",
"rand", "rand 0.9.1",
"sha1", "sha1",
"thiserror 2.0.12", "thiserror 2.0.12",
"utf-8", "utf-8",
@@ -1974,7 +2092,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
dependencies = [ dependencies = [
"getrandom 0.3.2", "getrandom 0.3.2",
"rand", "rand 0.9.1",
"uuid-macro-internal", "uuid-macro-internal",
] ]

View File

@@ -53,7 +53,8 @@ pub enum Error {
ClientError(String), ClientError(String),
HTTPError(hyper::Error), HTTPError(hyper::Error),
SerdeError(serde_json::Error), SerdeError(serde_json::Error),
DecodeError, DecodeError(String),
Unauthorized,
} }
impl std::error::Error for Error { impl std::error::Error for Error {
@@ -192,7 +193,7 @@ impl<K: AuthenticationStore + Send + Sync> APIInterface for HTTPAPIClient<K> {
let body = || -> Body { serde_json::to_string(&credentials).unwrap().into() }; 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: AuthResponse = self.request_with_body_retry("authenticate", Method::POST, body, false).await?;
let token = JwtToken::new(&token.jwt).map_err(|_| Error::DecodeError)?; let token = JwtToken::new(&token.jwt).map_err(|e| Error::DecodeError(e.to_string()))?;
log::debug!("Saving token: {:?}", token); log::debug!("Saving token: {:?}", token);
self.auth_store.set_token(token.clone()).await; self.auth_store.set_token(token.clone()).await;
@@ -239,7 +240,6 @@ impl<K: AuthenticationStore + Send + Sync> APIInterface for HTTPAPIClient<K> {
} }
async fn open_event_socket(&mut self) -> Result<WebsocketEventSocket, Self::Error> { async fn open_event_socket(&mut self) -> Result<WebsocketEventSocket, Self::Error> {
use tungstenite::http::StatusCode;
use tungstenite::handshake::client::Request as TungsteniteRequest; use tungstenite::handshake::client::Request as TungsteniteRequest;
use tungstenite::handshake::client::generate_key; use tungstenite::handshake::client::generate_key;
@@ -259,22 +259,46 @@ impl<K: AuthenticationStore + Send + Sync> APIInterface for HTTPAPIClient<K> {
.body(()) .body(())
.expect("Unable to build websocket request"); .expect("Unable to build websocket request");
log::debug!("Websocket request: {:?}", request); match &auth {
Some(token) => {
if let Some(token) = &auth {
let header_value = token.to_header_value().to_str().unwrap().parse().unwrap(); // ugh let header_value = token.to_header_value().to_str().unwrap().parse().unwrap(); // ugh
request.headers_mut().insert("Authorization", header_value); request.headers_mut().insert("Authorization", header_value);
} }
None => {
let (socket, response) = connect_async(request).await.map_err(Error::from)?; log::warn!(target: "websocket", "Proceeding without auth token.");
log::debug!("Websocket connected: {:?}", response.status()); }
if response.status() != StatusCode::SWITCHING_PROTOCOLS {
return Err(Error::ClientError("Websocket connection failed".into()));
} }
log::debug!("Websocket request: {:?}", request);
match connect_async(request).await.map_err(Error::from) {
Ok((socket, response)) => {
log::debug!("Websocket connected: {:?}", response.status());
Ok(WebsocketEventSocket::new(socket)) Ok(WebsocketEventSocket::new(socket))
} }
Err(e) => match e {
Error::ClientError(ce) => match ce.as_str() {
"HTTP error: 401 Unauthorized" | "Unauthorized" => {
// Try to authenticate
if let Some(credentials) = &self.auth_store.get_credentials().await {
log::warn!("Websocket connection failed, attempting to authenticate");
let new_token = self.authenticate(credentials.clone()).await?;
self.auth_store.set_token(new_token).await;
// try again on the next attempt.
return Err(Error::Unauthorized);
} else {
log::error!("Websocket unauthorized, no credentials provided");
return Err(Error::ClientError("Unauthorized, no credentials provided".into()));
}
}
_ => Err(Error::Unauthorized)
}
_ => Err(e)
}
}
}
} }
impl<K: AuthenticationStore + Send + Sync> HTTPAPIClient<K> { impl<K: AuthenticationStore + Send + Sync> HTTPAPIClient<K> {
@@ -384,7 +408,7 @@ impl<K: AuthenticationStore + Send + Sync> HTTPAPIClient<K> {
// If JSON deserialization fails, try to interpret it as plain text // If JSON deserialization fails, try to interpret it as plain text
// Unfortunately the server does return things like this... // Unfortunately the server does return things like this...
let s = str::from_utf8(&body).map_err(|_| Error::DecodeError)?; let s = str::from_utf8(&body).map_err(|e| Error::DecodeError(e.to_string()))?;
serde_plain::from_str(s).map_err(|_| json_err) serde_plain::from_str(s).map_err(|_| json_err)
} }
}?; }?;

View File

@@ -26,11 +26,31 @@ enum ExpValue {
#[derive(Deserialize, Serialize, Debug, Clone)] #[derive(Deserialize, Serialize, Debug, Clone)]
#[allow(dead_code)] #[allow(dead_code)]
struct JwtPayload { struct JwtPayload {
exp: serde_json::Value, #[serde(deserialize_with = "deserialize_exp")]
exp: i64,
iss: Option<String>, iss: Option<String>,
user: Option<String>, user: Option<String>,
} }
fn deserialize_exp<'de, D>(deserializer: D) -> Result<i64, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error;
#[derive(Deserialize)]
#[serde(untagged)]
enum ExpValue {
String(String),
Number(i64),
}
match ExpValue::deserialize(deserializer)? {
ExpValue::String(s) => s.parse().map_err(D::Error::custom),
ExpValue::Number(n) => Ok(n),
}
}
#[derive(Deserialize, Serialize, Debug, Clone)] #[derive(Deserialize, Serialize, Debug, Clone)]
#[allow(dead_code)] #[allow(dead_code)]
pub struct JwtToken { pub struct JwtToken {
@@ -62,13 +82,7 @@ impl JwtToken {
let payload: JwtPayload = serde_json::from_slice(&payload)?; let payload: JwtPayload = serde_json::from_slice(&payload)?;
// Parse jwt expiration date // Parse jwt expiration date
// Annoyingly, because of my own fault, this could be either an integer or string. let timestamp = DateTime::from_timestamp(payload.exp, 0).unwrap().naive_utc();
let exp: i64 = payload.exp.as_i64().unwrap_or_else(|| {
let exp: String = payload.exp.as_str().unwrap().to_string();
exp.parse().unwrap()
});
let timestamp = DateTime::from_timestamp(exp, 0).unwrap().naive_utc();
let expiration_date = DateTime::from_naive_utc_and_offset(timestamp, Utc); let expiration_date = DateTime::from_naive_utc_and_offset(timestamp, Utc);
Ok(JwtToken { Ok(JwtToken {
@@ -84,9 +98,19 @@ impl JwtToken {
// STUPID: My mock server uses a different encoding than the real server, so we have to // STUPID: My mock server uses a different encoding than the real server, so we have to
// try both encodings here. // try both encodings here.
Self::decode_token_using_engine(token, general_purpose::STANDARD).or( log::debug!("Attempting to decode JWT token: {}", token);
let result = Self::decode_token_using_engine(token, general_purpose::STANDARD).or(
Self::decode_token_using_engine(token, general_purpose::URL_SAFE_NO_PAD), Self::decode_token_using_engine(token, general_purpose::URL_SAFE_NO_PAD),
) );
if let Err(ref e) = result {
log::error!("Failed to decode JWT token: {}", e);
log::error!("Token length: {}", token.len());
log::error!("Token parts: {:?}", token.split('.').collect::<Vec<_>>());
}
result
} }
pub fn dummy() -> Self { pub fn dummy() -> Self {
@@ -96,7 +120,7 @@ impl JwtToken {
typ: "JWT".to_string(), typ: "JWT".to_string(),
}, },
payload: JwtPayload { payload: JwtPayload {
exp: serde_json::Value::Null, exp: 0,
iss: None, iss: None,
user: None, user: None,
}, },

View File

@@ -13,6 +13,7 @@ dbus-tree = "0.9.2"
directories = "6.0.0" directories = "6.0.0"
env_logger = "0.11.6" env_logger = "0.11.6"
futures-util = "0.3.31" futures-util = "0.3.31"
keyring = { version = "3.6.2", features = ["sync-secret-service"] }
kordophone = { path = "../kordophone" } kordophone = { path = "../kordophone" }
kordophone-db = { path = "../kordophone-db" } kordophone-db = { path = "../kordophone-db" }
log = "0.4.25" log = "0.4.25"

View File

@@ -80,12 +80,6 @@
<property name="ServerURL" type="s" access="readwrite"/> <property name="ServerURL" type="s" access="readwrite"/>
<property name="Username" type="s" access="readwrite"/> <property name="Username" type="s" access="readwrite"/>
<!-- Secret-Service handle (object path) -->
<property name="CredentialItem" type="o" access="readwrite">
<annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal"
value="true"/>
</property>
<!-- helpers for atomic updates --> <!-- helpers for atomic updates -->
<method name="SetServer"> <method name="SetServer">
<arg name="url" type="s" direction="in"/> <arg name="url" type="s" direction="in"/>

View File

@@ -2,6 +2,7 @@ use crate::daemon::SettingsKey;
use std::sync::Arc; use std::sync::Arc;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use keyring::{Entry, Result};
use kordophone::api::{AuthenticationStore, http_client::Credentials}; use kordophone::api::{AuthenticationStore, http_client::Credentials};
use kordophone::model::JwtToken; use kordophone::model::JwtToken;
@@ -22,6 +23,8 @@ impl DatabaseAuthenticationStore {
#[async_trait] #[async_trait]
impl AuthenticationStore for DatabaseAuthenticationStore { impl AuthenticationStore for DatabaseAuthenticationStore {
async fn get_credentials(&mut self) -> Option<Credentials> { async fn get_credentials(&mut self) -> Option<Credentials> {
use keyring::secret_service::SsCredential;
self.database.lock().await.with_settings(|settings| { self.database.lock().await.with_settings(|settings| {
let username: Option<String> = settings.get::<String>(SettingsKey::USERNAME) let username: Option<String> = settings.get::<String>(SettingsKey::USERNAME)
.unwrap_or_else(|e| { .unwrap_or_else(|e| {
@@ -29,31 +32,39 @@ impl AuthenticationStore for DatabaseAuthenticationStore {
None None
}); });
// TODO: This would be the point where we map from credential item to password. match username {
let password: String = settings.get::<String>(SettingsKey::CREDENTIAL_ITEM) Some(username) => {
.unwrap_or_else(|e| { let credential = SsCredential::new_with_target(None, "net.buzzert.kordophonecd", &username).unwrap();
log::warn!("error getting password from database: {}", e);
let password: Result<String> = Entry::new_with_credential(Box::new(credential))
.get_password();
log::debug!("password: {:?}", password);
match password {
Ok(password) => Some(Credentials { username, password }),
Err(e) => {
log::error!("error getting password from keyring: {}", e);
None None
})
.unwrap_or_else(|| {
log::warn!("warning: no password in database, [DEBUG] using default password");
"test".to_string()
});
if username.is_none() {
log::warn!("Username not present in database");
} }
}
match (username, password) { }
(Some(username), password) => Some(Credentials { username, password }), None => None,
_ => None,
} }
}).await }).await
} }
async fn get_token(&mut self) -> Option<JwtToken> { async fn get_token(&mut self) -> Option<JwtToken> {
self.database.lock().await self.database.lock().await
.with_settings(|settings| settings.get::<JwtToken>(SettingsKey::TOKEN).unwrap_or_default()).await .with_settings(|settings| {
match settings.get::<JwtToken>(SettingsKey::TOKEN) {
Ok(token) => token,
Err(e) => {
log::warn!("Failed to get token from settings: {}", e);
None
}
}
}).await
} }
async fn set_token(&mut self, token: JwtToken) { async fn set_token(&mut self, token: JwtToken) {

View File

@@ -51,6 +51,7 @@ pub mod target {
pub static SETTINGS: &str = "settings"; pub static SETTINGS: &str = "settings";
pub static UPDATES: &str = "updates"; pub static UPDATES: &str = "updates";
} }
pub struct Daemon { pub struct Daemon {
pub event_sender: Sender<Event>, pub event_sender: Sender<Event>,
event_receiver: Receiver<Event>, event_receiver: Receiver<Event>,

View File

@@ -4,7 +4,6 @@ use anyhow::Result;
pub mod keys { pub mod keys {
pub static SERVER_URL: &str = "ServerURL"; pub static SERVER_URL: &str = "ServerURL";
pub static USERNAME: &str = "Username"; pub static USERNAME: &str = "Username";
pub static CREDENTIAL_ITEM: &str = "CredentialItem";
pub static TOKEN: &str = "Token"; pub static TOKEN: &str = "Token";
} }
@@ -13,7 +12,6 @@ pub mod keys {
pub struct Settings { pub struct Settings {
pub server_url: Option<String>, pub server_url: Option<String>,
pub username: Option<String>, pub username: Option<String>,
pub credential_item: Option<String>,
pub token: Option<String>, pub token: Option<String>,
} }
@@ -21,12 +19,10 @@ impl Settings {
pub fn from_db(db_settings: &mut DbSettings) -> Result<Self> { pub fn from_db(db_settings: &mut DbSettings) -> Result<Self> {
let server_url: Option<String> = db_settings.get(keys::SERVER_URL)?; let server_url: Option<String> = db_settings.get(keys::SERVER_URL)?;
let username: Option<String> = db_settings.get(keys::USERNAME)?; let username: Option<String> = db_settings.get(keys::USERNAME)?;
let credential_item: Option<String> = db_settings.get(keys::CREDENTIAL_ITEM)?;
let token: Option<String> = db_settings.get(keys::TOKEN)?; let token: Option<String> = db_settings.get(keys::TOKEN)?;
Ok(Self { Ok(Self {
server_url, server_url,
username, username,
credential_item,
token, token,
}) })
} }
@@ -38,9 +34,6 @@ impl Settings {
if let Some(username) = &self.username { if let Some(username) = &self.username {
db_settings.put(keys::USERNAME, &username)?; db_settings.put(keys::USERNAME, &username)?;
} }
if let Some(credential_item) = &self.credential_item {
db_settings.put(keys::CREDENTIAL_ITEM, &credential_item)?;
}
if let Some(token) = &self.token { if let Some(token) = &self.token {
db_settings.put(keys::TOKEN, &token)?; db_settings.put(keys::TOKEN, &token)?;
} }

View File

@@ -115,7 +115,6 @@ impl DbusSettings for ServerImpl {
Event::UpdateSettings(Settings { Event::UpdateSettings(Settings {
server_url: Some(url), server_url: Some(url),
username: Some(user), username: Some(user),
credential_item: None,
token: None, token: None,
}, r) }, r)
) )
@@ -131,7 +130,6 @@ impl DbusSettings for ServerImpl {
Event::UpdateSettings(Settings { Event::UpdateSettings(Settings {
server_url: Some(value), server_url: Some(value),
username: None, username: None,
credential_item: None,
token: None, token: None,
}, r) }, r)
) )
@@ -147,28 +145,10 @@ impl DbusSettings for ServerImpl {
Event::UpdateSettings(Settings { Event::UpdateSettings(Settings {
server_url: None, server_url: None,
username: Some(value), username: Some(value),
credential_item: None,
token: None, token: None,
}, r) }, r)
) )
} }
fn credential_item(&self) -> Result<dbus::Path<'static>, dbus::MethodErr> {
self.send_event_sync(Event::GetAllSettings)
.map(|settings| settings.credential_item.unwrap_or_default()).map(|item| dbus::Path::new(item).unwrap_or_default())
}
fn set_credential_item(&self, value: dbus::Path<'static>) -> Result<(), dbus::MethodErr> {
self.send_event_sync(|r|
Event::UpdateSettings(Settings {
server_url: None,
username: None,
credential_item: Some(value.to_string()),
token: None,
}, r)
)
}
} }
fn run_sync_future<F, T>(f: F) -> Result<T, MethodErr> fn run_sync_future<F, T>(f: F) -> Result<T, MethodErr>

View File

@@ -70,11 +70,6 @@ pub enum ConfigCommands {
SetUsername { SetUsername {
username: String, username: String,
}, },
/// Sets the credential item.
SetCredentialItem {
item: String,
},
} }
impl Commands { impl Commands {
@@ -180,19 +175,16 @@ impl DaemonCli {
ConfigCommands::Print => self.print_settings().await, ConfigCommands::Print => self.print_settings().await,
ConfigCommands::SetServerUrl { url } => self.set_server_url(url).await, ConfigCommands::SetServerUrl { url } => self.set_server_url(url).await,
ConfigCommands::SetUsername { username } => self.set_username(username).await, ConfigCommands::SetUsername { username } => self.set_username(username).await,
ConfigCommands::SetCredentialItem { item } => self.set_credential_item(item).await,
} }
} }
pub async fn print_settings(&mut self) -> Result<()> { pub async fn print_settings(&mut self) -> Result<()> {
let server_url = KordophoneSettings::server_url(&self.proxy()).unwrap_or_default(); let server_url = KordophoneSettings::server_url(&self.proxy()).unwrap_or_default();
let username = KordophoneSettings::username(&self.proxy()).unwrap_or_default(); let username = KordophoneSettings::username(&self.proxy()).unwrap_or_default();
let credential_item = KordophoneSettings::credential_item(&self.proxy()).unwrap_or_default();
let table = table!( let table = table!(
[ b->"Server URL", &server_url ], [ b->"Server URL", &server_url ],
[ b->"Username", &username ], [ b->"Username", &username ]
[ b->"Credential Item", &credential_item ]
); );
table.printstd(); table.printstd();
@@ -209,11 +201,6 @@ impl DaemonCli {
.map_err(|e| anyhow::anyhow!("Failed to set username: {}", e)) .map_err(|e| anyhow::anyhow!("Failed to set username: {}", e))
} }
pub async fn set_credential_item(&mut self, item: String) -> Result<()> {
KordophoneSettings::set_credential_item(&self.proxy(), item.into())
.map_err(|e| anyhow::anyhow!("Failed to set credential item: {}", e))
}
pub async fn delete_all_conversations(&mut self) -> Result<()> { pub async fn delete_all_conversations(&mut self) -> Result<()> {
KordophoneRepository::delete_all_conversations(&self.proxy()) KordophoneRepository::delete_all_conversations(&self.proxy())
.map_err(|e| anyhow::anyhow!("Failed to delete all conversations: {}", e)) .map_err(|e| anyhow::anyhow!("Failed to delete all conversations: {}", e))

View File

@@ -93,15 +93,17 @@ struct DbClient {
impl DbClient { impl DbClient {
fn database_path() -> PathBuf { fn database_path() -> PathBuf {
env::var("KORDOPHONE_DB_PATH").unwrap_or_else(|_| {
let temp_dir = env::temp_dir(); let temp_dir = env::temp_dir();
temp_dir.join("kpcli_chat.db") temp_dir.join("kpcli_chat.db").to_str().unwrap().to_string()
}).into()
} }
pub fn new() -> Result<Self> { pub fn new() -> Result<Self> {
let path = Self::database_path(); let path = Self::database_path();
let path_str: &str = path.as_path().to_str().unwrap(); let path_str: &str = path.as_path().to_str().unwrap();
println!("kpcli: Using temporary db at {}", path_str); println!("kpcli: Using db at {}", path_str);
let db = Database::new(path_str)?; let db = Database::new(path_str)?;
Ok( Self { database: db }) Ok( Self { database: db })