mirror of
https://gitea.mizah.xyz/mizah/dergchat
synced 2024-11-21 13:45:00 -05:00
Did a bunch of refactoring.
This commit is contained in:
parent
cbb307e8a2
commit
5a33bf2280
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -2212,6 +2212,7 @@ checksum = "b4a52cacd869b804660986b10aa2076c3a4b6da644c7198f9fd0b613f4a7b249"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minidom",
|
||||
"serde",
|
||||
"stringprep",
|
||||
]
|
||||
|
||||
|
@ -31,7 +31,7 @@ dioxus-hot-reload = { version="0.4.3", features=["file_watcher"] }
|
||||
futures-util = "0.3.29"
|
||||
xmpp = { git = "https://gitlab.com/werner.kroneman/xmpp-rs.git", rev="ecd0be4aad985e9812626d6c4499c2586c158aba"}
|
||||
keyring = "2.1.0"
|
||||
jid = "0.10.0"
|
||||
jid = { version = "0.10.0", features = ["serde"] }
|
||||
confy = "0.5.1"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
@ -16,15 +16,17 @@
|
||||
|
||||
use crate::widgets::login_screen::LoginAttempt;
|
||||
use dioxus::prelude::*;
|
||||
use jid::BareJid;
|
||||
use keyring::Entry;
|
||||
use log::error;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use crate::passwords::Password;
|
||||
|
||||
/// The configuration struct containing all the configuration options.
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
pub struct Configuration {
|
||||
pub stored_username: String,
|
||||
pub stored_default_nick: String,
|
||||
pub stored_username: Option<BareJid>,
|
||||
pub stored_default_nick: Option<String>,
|
||||
}
|
||||
|
||||
/// Cache the login attempt into the config and keyring.
|
||||
@ -36,30 +38,25 @@ pub struct Configuration {
|
||||
/// # Arguments
|
||||
/// * `attempt` - The login attempt to store.
|
||||
/// * `config` - The current app configuration; UseState should prevent race conditions in writing.
|
||||
pub fn store_login_details(attempt: LoginAttempt, config: &mut UseRef<Configuration>) {
|
||||
// Extract the fields from the login attempt.
|
||||
let LoginAttempt {
|
||||
username,
|
||||
default_nick,
|
||||
password,
|
||||
} = attempt;
|
||||
pub fn store_login_details(jid: BareJid,
|
||||
default_nick: String,
|
||||
password: &Password,
|
||||
c: &mut Configuration) {
|
||||
|
||||
// Open the config state and store the username and default nick.
|
||||
config.with_mut(|c| {
|
||||
// Open the keyring and store the password.
|
||||
let entry = Entry::new("dergchat", &username).expect("Failed to create keyring entry.");
|
||||
let entry = Entry::new("dergchat", &jid.to_string()).expect("Failed to create keyring entry.");
|
||||
entry
|
||||
.set_password(&password)
|
||||
.set_password(&password.0)
|
||||
.expect("Failed to set password in keyring.");
|
||||
|
||||
// Store the username and default nick in the config.
|
||||
c.stored_username = username;
|
||||
c.stored_default_nick = default_nick;
|
||||
c.stored_username = Some(jid);
|
||||
c.stored_default_nick = Some(default_nick);
|
||||
|
||||
if let Err(e) = confy::store("dergchat", None, c) {
|
||||
error!("Failed to store the config file: {}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Load the configuration from the config file.
|
||||
|
@ -20,6 +20,7 @@ mod configuration;
|
||||
mod types;
|
||||
mod widgets;
|
||||
mod xmpp_interface;
|
||||
mod passwords;
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_desktop::Config;
|
||||
|
40
src/passwords.rs
Normal file
40
src/passwords.rs
Normal file
@ -0,0 +1,40 @@
|
||||
// Dergchat, a free XMPP client.
|
||||
// Copyright (C) 2023 Werner Kroneman
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use std::fmt::Debug;
|
||||
use jid::BareJid;
|
||||
use keyring::Entry;
|
||||
|
||||
pub struct Password(pub String);
|
||||
|
||||
impl Debug for Password {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("Password")
|
||||
.field("password", &"********")
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieve the password for the given username from the keyring, if it exists.
|
||||
pub fn try_retrieve_password_from_keyring(username: &BareJid) -> Option<Password> {
|
||||
Entry::new("dergchat", &username.to_string()).expect("Failed to create keyring entry.").get_password().ok().map(|p| Password(p))
|
||||
}
|
||||
|
||||
/// Store the password for the given username in the keyring.
|
||||
pub fn store_keyring_password(username: &BareJid, password: &Password) {
|
||||
let entry = Entry::new("dergchat", &username.to_string()).expect("Failed to create keyring entry.");
|
||||
entry.set_password(&*password.0).expect("Failed to set password in keyring.");
|
||||
}
|
16
src/types.rs
16
src/types.rs
@ -16,6 +16,7 @@
|
||||
|
||||
use jid::BareJid;
|
||||
use std::fmt::Debug;
|
||||
use crate::passwords::Password;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Room {
|
||||
@ -28,18 +29,9 @@ pub struct Message {
|
||||
pub body: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LoginCredentials {
|
||||
pub username: BareJid,
|
||||
pub default_nick: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
impl Debug for LoginCredentials {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("LoginCredentials")
|
||||
.field("username", &self.username)
|
||||
.field("default_nick", &self.default_nick)
|
||||
.field("password", &"********")
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
pub password: Password,
|
||||
}
|
@ -22,6 +22,7 @@ use crate::widgets::room_view::RoomView;
|
||||
use crate::widgets::sidebar::SideBar;
|
||||
use crate::xmpp_interface::NetworkCommand;
|
||||
use dioxus::core::{Element, Scope};
|
||||
use crate::passwords::Password;
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use futures_util::StreamExt;
|
||||
@ -29,21 +30,42 @@ use jid::BareJid;
|
||||
use log::{error, info};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::configuration::load_config;
|
||||
use crate::configuration::{Configuration, load_config};
|
||||
use crate::xmpp_interface;
|
||||
use keyring::Entry;
|
||||
use std::str::FromStr;
|
||||
use std::string::String;
|
||||
use crate::passwords::try_retrieve_password_from_keyring;
|
||||
use crate::configuration::store_login_details;
|
||||
|
||||
pub mod login_screen;
|
||||
mod no_room_open;
|
||||
pub mod no_room_open;
|
||||
pub mod room_join_widget;
|
||||
pub mod room_list;
|
||||
pub mod room_view;
|
||||
pub mod send_message;
|
||||
pub mod sidebar;
|
||||
|
||||
fn try_retrieve_credentials(config: &Configuration) -> Option<LoginCredentials> {
|
||||
if let (Some(user), Some(nick)) = (&config.stored_username, &config.stored_default_nick) {
|
||||
if let Some(password) = try_retrieve_password_from_keyring(&user) {
|
||||
Some(LoginCredentials {
|
||||
username: user.clone(),
|
||||
default_nick: nick.clone(),
|
||||
password,
|
||||
})
|
||||
} else {
|
||||
info!("No stored password found; will not try to log in.");
|
||||
None
|
||||
}
|
||||
} else {
|
||||
info!("No stored username or default nick found; will not try to log in.");
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn App(cx: Scope) -> Element {
|
||||
|
||||
let messages = use_ref(cx, || HashMap::new());
|
||||
let current_room = use_state(cx, || None::<BareJid>);
|
||||
let connection_status = use_state(cx, || LoginStatus::LoggedOut);
|
||||
@ -54,41 +76,19 @@ pub fn App(cx: Scope) -> Element {
|
||||
|
||||
let config = use_ref(cx, load_config);
|
||||
|
||||
{
|
||||
let config = config.to_owned();
|
||||
let _connection_status = connection_status.to_owned();
|
||||
let coroutine = coroutine.to_owned();
|
||||
|
||||
use_on_create(cx, move || {
|
||||
use_on_create(cx, || {
|
||||
let config = config.to_owned();
|
||||
let _connection_status = connection_status.to_owned();
|
||||
let coroutine = coroutine.to_owned();
|
||||
async move {
|
||||
let entry = Entry::new("dergchat", &config.read().stored_username)
|
||||
.expect("Failed to create keyring entry.");
|
||||
|
||||
let _password = match entry.get_password() {
|
||||
Ok(_x) => {
|
||||
info!("Password retrieved from keyring; will try to log in.");
|
||||
|
||||
let (username, default_nick) = config
|
||||
.with(|c| (c.stored_username.clone(), c.stored_default_nick.clone()));
|
||||
|
||||
// If we have a username, nickname and password, immediately try to log in.
|
||||
if username.len() > 0 && default_nick.len() > 0 {
|
||||
let credentials = LoginCredentials {
|
||||
username: BareJid::from_str(&username).expect("Invalid JID"),
|
||||
default_nick: default_nick,
|
||||
password: entry
|
||||
.get_password()
|
||||
.expect("Failed to get password from keyring"),
|
||||
};
|
||||
|
||||
coroutine.send(NetworkCommand::TryLogin { credentials });
|
||||
}
|
||||
}
|
||||
Err(e) => println!("Failed to get password from keyring: {}", e),
|
||||
};
|
||||
if let Some(credentials) = try_retrieve_credentials(&*config.read()) {
|
||||
info!("Will try to log in as {} with default nick {}", credentials.username, credentials.default_nick);
|
||||
coroutine.send(NetworkCommand::TryLogin { credentials });
|
||||
} else {
|
||||
info!("No stored credentials found; will not try to automatically log in.");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render! {
|
||||
|
||||
@ -97,33 +97,36 @@ pub fn App(cx: Scope) -> Element {
|
||||
|
||||
rsx! {
|
||||
LoginScreen {
|
||||
cached_username: config.read().stored_username.clone(),
|
||||
cached_nick: config.read().stored_default_nick.clone(),
|
||||
cached_username: config.read().stored_username.clone().map(|x| x.to_string()).unwrap_or("".to_string()),
|
||||
cached_nick: config.read().stored_default_nick.clone().unwrap_or("".to_string()),
|
||||
login_state: connection_status.current().as_ref().clone(),
|
||||
on_login_attempt: move |x: LoginAttempt| {
|
||||
|
||||
{
|
||||
let x = x.clone();
|
||||
// Store the JID in the PKV.
|
||||
config.with_mut(move |c| {
|
||||
c.stored_username = x.username.clone();
|
||||
c.stored_default_nick = x.default_nick.clone();
|
||||
let LoginAttempt { username, default_nick, password } = x;
|
||||
|
||||
// Store the password in the keyring.
|
||||
let entry = Entry::new("dergchat", &x.username).expect("Failed to create keyring entry.");
|
||||
entry.set_password(&x.password).expect("Failed to set password in keyring.");
|
||||
// First, validate the username as a jid:
|
||||
let jid = match BareJid::from_str(&username) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
error!("Invalid JID: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = confy::store("dergchat", None, &*config.read()) {
|
||||
error!("Failed to store JID in config file: {}", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
// Wrap the password in a Password struct.
|
||||
let password = Password(password);
|
||||
|
||||
// Store the login details in the config and keyring for auto-login next time.
|
||||
config.with_mut(|c| {
|
||||
store_login_details(jid.clone(), default_nick.clone(), &password, c);
|
||||
});
|
||||
|
||||
// Finally, send the login command to the xmpp interface.
|
||||
coroutine.send(NetworkCommand::TryLogin {
|
||||
credentials: LoginCredentials {
|
||||
username: BareJid::from_str(&x.username).expect("Invalid JID"),
|
||||
default_nick: x.default_nick,
|
||||
password: x.password,
|
||||
username: jid,
|
||||
default_nick,
|
||||
password,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
@ -203,7 +203,7 @@ pub async fn run_xmpp_toplevel(
|
||||
|
||||
connection_status.set(LoginStatus::LoggingIn);
|
||||
|
||||
let mut agent = ClientBuilder::new(credentials.username, &*credentials.password)
|
||||
let mut agent = ClientBuilder::new(credentials.username, &credentials.password.0)
|
||||
.set_client(ClientType::Pc, "dergchat")
|
||||
.build();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user