implements authentication/token retrieval/keyring
This commit is contained in:
128
Cargo.lock
generated
128
Cargo.lock
generated
@@ -430,6 +430,19 @@ dependencies = [
|
||||
"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]]
|
||||
name = "dbus-tokio"
|
||||
version = "0.7.6"
|
||||
@@ -976,6 +989,16 @@ dependencies = [
|
||||
"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]]
|
||||
name = "kordophone"
|
||||
version = "0.1.0"
|
||||
@@ -1031,6 +1054,7 @@ dependencies = [
|
||||
"directories",
|
||||
"env_logger",
|
||||
"futures-util",
|
||||
"keyring",
|
||||
"kordophone",
|
||||
"kordophone-db",
|
||||
"log",
|
||||
@@ -1190,12 +1214,76 @@ dependencies = [
|
||||
"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]]
|
||||
name = "num-conv"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
@@ -1388,14 +1476,35 @@ version = "5.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "rand"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
|
||||
dependencies = [
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"rand_chacha 0.9.0",
|
||||
"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]]
|
||||
@@ -1405,7 +1514,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"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]]
|
||||
@@ -1925,7 +2043,7 @@ dependencies = [
|
||||
"http 1.3.1",
|
||||
"httparse",
|
||||
"log",
|
||||
"rand",
|
||||
"rand 0.9.1",
|
||||
"sha1",
|
||||
"thiserror 2.0.12",
|
||||
"utf-8",
|
||||
@@ -1974,7 +2092,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
|
||||
dependencies = [
|
||||
"getrandom 0.3.2",
|
||||
"rand",
|
||||
"rand 0.9.1",
|
||||
"uuid-macro-internal",
|
||||
]
|
||||
|
||||
|
||||
@@ -53,7 +53,8 @@ pub enum Error {
|
||||
ClientError(String),
|
||||
HTTPError(hyper::Error),
|
||||
SerdeError(serde_json::Error),
|
||||
DecodeError,
|
||||
DecodeError(String),
|
||||
Unauthorized,
|
||||
}
|
||||
|
||||
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 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);
|
||||
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> {
|
||||
use tungstenite::http::StatusCode;
|
||||
use tungstenite::handshake::client::Request as TungsteniteRequest;
|
||||
use tungstenite::handshake::client::generate_key;
|
||||
|
||||
@@ -259,21 +259,45 @@ impl<K: AuthenticationStore + Send + Sync> APIInterface for HTTPAPIClient<K> {
|
||||
.body(())
|
||||
.expect("Unable to build websocket request");
|
||||
|
||||
match &auth {
|
||||
Some(token) => {
|
||||
let header_value = token.to_header_value().to_str().unwrap().parse().unwrap(); // ugh
|
||||
request.headers_mut().insert("Authorization", header_value);
|
||||
}
|
||||
None => {
|
||||
log::warn!(target: "websocket", "Proceeding without auth token.");
|
||||
}
|
||||
}
|
||||
|
||||
log::debug!("Websocket request: {:?}", request);
|
||||
|
||||
if let Some(token) = &auth {
|
||||
let header_value = token.to_header_value().to_str().unwrap().parse().unwrap(); // ugh
|
||||
request.headers_mut().insert("Authorization", header_value);
|
||||
match connect_async(request).await.map_err(Error::from) {
|
||||
Ok((socket, response)) => {
|
||||
log::debug!("Websocket connected: {:?}", response.status());
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
let (socket, response) = connect_async(request).await.map_err(Error::from)?;
|
||||
log::debug!("Websocket connected: {:?}", response.status());
|
||||
|
||||
if response.status() != StatusCode::SWITCHING_PROTOCOLS {
|
||||
return Err(Error::ClientError("Websocket connection failed".into()));
|
||||
}
|
||||
|
||||
Ok(WebsocketEventSocket::new(socket))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -384,7 +408,7 @@ impl<K: AuthenticationStore + Send + Sync> HTTPAPIClient<K> {
|
||||
|
||||
// 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)?;
|
||||
let s = str::from_utf8(&body).map_err(|e| Error::DecodeError(e.to_string()))?;
|
||||
serde_plain::from_str(s).map_err(|_| json_err)
|
||||
}
|
||||
}?;
|
||||
|
||||
@@ -26,11 +26,31 @@ enum ExpValue {
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
#[allow(dead_code)]
|
||||
struct JwtPayload {
|
||||
exp: serde_json::Value,
|
||||
#[serde(deserialize_with = "deserialize_exp")]
|
||||
exp: i64,
|
||||
iss: 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)]
|
||||
#[allow(dead_code)]
|
||||
pub struct JwtToken {
|
||||
@@ -62,13 +82,7 @@ impl JwtToken {
|
||||
let payload: JwtPayload = serde_json::from_slice(&payload)?;
|
||||
|
||||
// Parse jwt expiration date
|
||||
// Annoyingly, because of my own fault, this could be either an integer or string.
|
||||
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 timestamp = DateTime::from_timestamp(payload.exp, 0).unwrap().naive_utc();
|
||||
let expiration_date = DateTime::from_naive_utc_and_offset(timestamp, Utc);
|
||||
|
||||
Ok(JwtToken {
|
||||
@@ -84,9 +98,19 @@ impl JwtToken {
|
||||
// STUPID: My mock server uses a different encoding than the real server, so we have to
|
||||
// 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),
|
||||
)
|
||||
);
|
||||
|
||||
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 {
|
||||
@@ -96,7 +120,7 @@ impl JwtToken {
|
||||
typ: "JWT".to_string(),
|
||||
},
|
||||
payload: JwtPayload {
|
||||
exp: serde_json::Value::Null,
|
||||
exp: 0,
|
||||
iss: None,
|
||||
user: None,
|
||||
},
|
||||
|
||||
@@ -13,6 +13,7 @@ dbus-tree = "0.9.2"
|
||||
directories = "6.0.0"
|
||||
env_logger = "0.11.6"
|
||||
futures-util = "0.3.31"
|
||||
keyring = { version = "3.6.2", features = ["sync-secret-service"] }
|
||||
kordophone = { path = "../kordophone" }
|
||||
kordophone-db = { path = "../kordophone-db" }
|
||||
log = "0.4.25"
|
||||
|
||||
@@ -80,12 +80,6 @@
|
||||
<property name="ServerURL" 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 -->
|
||||
<method name="SetServer">
|
||||
<arg name="url" type="s" direction="in"/>
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::daemon::SettingsKey;
|
||||
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
use keyring::{Entry, Result};
|
||||
|
||||
use kordophone::api::{AuthenticationStore, http_client::Credentials};
|
||||
use kordophone::model::JwtToken;
|
||||
@@ -22,6 +23,8 @@ impl DatabaseAuthenticationStore {
|
||||
#[async_trait]
|
||||
impl AuthenticationStore for DatabaseAuthenticationStore {
|
||||
async fn get_credentials(&mut self) -> Option<Credentials> {
|
||||
use keyring::secret_service::SsCredential;
|
||||
|
||||
self.database.lock().await.with_settings(|settings| {
|
||||
let username: Option<String> = settings.get::<String>(SettingsKey::USERNAME)
|
||||
.unwrap_or_else(|e| {
|
||||
@@ -29,31 +32,39 @@ impl AuthenticationStore for DatabaseAuthenticationStore {
|
||||
None
|
||||
});
|
||||
|
||||
// TODO: This would be the point where we map from credential item to password.
|
||||
let password: String = settings.get::<String>(SettingsKey::CREDENTIAL_ITEM)
|
||||
.unwrap_or_else(|e| {
|
||||
log::warn!("error getting password from database: {}", e);
|
||||
None
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
log::warn!("warning: no password in database, [DEBUG] using default password");
|
||||
"test".to_string()
|
||||
});
|
||||
match username {
|
||||
Some(username) => {
|
||||
let credential = SsCredential::new_with_target(None, "net.buzzert.kordophonecd", &username).unwrap();
|
||||
|
||||
if username.is_none() {
|
||||
log::warn!("Username not present in database");
|
||||
}
|
||||
let password: Result<String> = Entry::new_with_credential(Box::new(credential))
|
||||
.get_password();
|
||||
|
||||
match (username, password) {
|
||||
(Some(username), password) => Some(Credentials { username, password }),
|
||||
_ => None,
|
||||
log::debug!("password: {:?}", password);
|
||||
|
||||
match password {
|
||||
Ok(password) => Some(Credentials { username, password }),
|
||||
Err(e) => {
|
||||
log::error!("error getting password from keyring: {}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}).await
|
||||
}
|
||||
|
||||
async fn get_token(&mut self) -> Option<JwtToken> {
|
||||
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) {
|
||||
|
||||
@@ -51,6 +51,7 @@ pub mod target {
|
||||
pub static SETTINGS: &str = "settings";
|
||||
pub static UPDATES: &str = "updates";
|
||||
}
|
||||
|
||||
pub struct Daemon {
|
||||
pub event_sender: Sender<Event>,
|
||||
event_receiver: Receiver<Event>,
|
||||
|
||||
@@ -4,7 +4,6 @@ use anyhow::Result;
|
||||
pub mod keys {
|
||||
pub static SERVER_URL: &str = "ServerURL";
|
||||
pub static USERNAME: &str = "Username";
|
||||
pub static CREDENTIAL_ITEM: &str = "CredentialItem";
|
||||
pub static TOKEN: &str = "Token";
|
||||
}
|
||||
|
||||
@@ -13,7 +12,6 @@ pub mod keys {
|
||||
pub struct Settings {
|
||||
pub server_url: Option<String>,
|
||||
pub username: Option<String>,
|
||||
pub credential_item: Option<String>,
|
||||
pub token: Option<String>,
|
||||
}
|
||||
|
||||
@@ -21,12 +19,10 @@ impl Settings {
|
||||
pub fn from_db(db_settings: &mut DbSettings) -> Result<Self> {
|
||||
let server_url: Option<String> = db_settings.get(keys::SERVER_URL)?;
|
||||
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)?;
|
||||
Ok(Self {
|
||||
server_url,
|
||||
username,
|
||||
credential_item,
|
||||
token,
|
||||
})
|
||||
}
|
||||
@@ -38,9 +34,6 @@ impl Settings {
|
||||
if let Some(username) = &self.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 {
|
||||
db_settings.put(keys::TOKEN, &token)?;
|
||||
}
|
||||
|
||||
@@ -115,7 +115,6 @@ impl DbusSettings for ServerImpl {
|
||||
Event::UpdateSettings(Settings {
|
||||
server_url: Some(url),
|
||||
username: Some(user),
|
||||
credential_item: None,
|
||||
token: None,
|
||||
}, r)
|
||||
)
|
||||
@@ -131,7 +130,6 @@ impl DbusSettings for ServerImpl {
|
||||
Event::UpdateSettings(Settings {
|
||||
server_url: Some(value),
|
||||
username: None,
|
||||
credential_item: None,
|
||||
token: None,
|
||||
}, r)
|
||||
)
|
||||
@@ -147,28 +145,10 @@ impl DbusSettings for ServerImpl {
|
||||
Event::UpdateSettings(Settings {
|
||||
server_url: None,
|
||||
username: Some(value),
|
||||
credential_item: None,
|
||||
token: None,
|
||||
}, 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>
|
||||
|
||||
@@ -70,11 +70,6 @@ pub enum ConfigCommands {
|
||||
SetUsername {
|
||||
username: String,
|
||||
},
|
||||
|
||||
/// Sets the credential item.
|
||||
SetCredentialItem {
|
||||
item: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl Commands {
|
||||
@@ -180,19 +175,16 @@ impl DaemonCli {
|
||||
ConfigCommands::Print => self.print_settings().await,
|
||||
ConfigCommands::SetServerUrl { url } => self.set_server_url(url).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<()> {
|
||||
let server_url = KordophoneSettings::server_url(&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!(
|
||||
[ b->"Server URL", &server_url ],
|
||||
[ b->"Username", &username ],
|
||||
[ b->"Credential Item", &credential_item ]
|
||||
[ b->"Username", &username ]
|
||||
);
|
||||
table.printstd();
|
||||
|
||||
@@ -209,11 +201,6 @@ impl DaemonCli {
|
||||
.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<()> {
|
||||
KordophoneRepository::delete_all_conversations(&self.proxy())
|
||||
.map_err(|e| anyhow::anyhow!("Failed to delete all conversations: {}", e))
|
||||
|
||||
@@ -93,15 +93,17 @@ struct DbClient {
|
||||
|
||||
impl DbClient {
|
||||
fn database_path() -> PathBuf {
|
||||
let temp_dir = env::temp_dir();
|
||||
temp_dir.join("kpcli_chat.db")
|
||||
env::var("KORDOPHONE_DB_PATH").unwrap_or_else(|_| {
|
||||
let temp_dir = env::temp_dir();
|
||||
temp_dir.join("kpcli_chat.db").to_str().unwrap().to_string()
|
||||
}).into()
|
||||
}
|
||||
|
||||
pub fn new() -> Result<Self> {
|
||||
let path = Self::database_path();
|
||||
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)?;
|
||||
Ok( Self { database: db })
|
||||
|
||||
Reference in New Issue
Block a user