mirror of
https://gitea.mizah.xyz/mizah/dergchat
synced 2024-11-24 15:12:16 -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 = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
"minidom",
|
"minidom",
|
||||||
|
"serde",
|
||||||
"stringprep",
|
"stringprep",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ dioxus-hot-reload = { version="0.4.3", features=["file_watcher"] }
|
|||||||
futures-util = "0.3.29"
|
futures-util = "0.3.29"
|
||||||
xmpp = { git = "https://gitlab.com/werner.kroneman/xmpp-rs.git", rev="ecd0be4aad985e9812626d6c4499c2586c158aba"}
|
xmpp = { git = "https://gitlab.com/werner.kroneman/xmpp-rs.git", rev="ecd0be4aad985e9812626d6c4499c2586c158aba"}
|
||||||
keyring = "2.1.0"
|
keyring = "2.1.0"
|
||||||
jid = "0.10.0"
|
jid = { version = "0.10.0", features = ["serde"] }
|
||||||
confy = "0.5.1"
|
confy = "0.5.1"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
@ -16,15 +16,17 @@
|
|||||||
|
|
||||||
use crate::widgets::login_screen::LoginAttempt;
|
use crate::widgets::login_screen::LoginAttempt;
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
use jid::BareJid;
|
||||||
use keyring::Entry;
|
use keyring::Entry;
|
||||||
use log::error;
|
use log::error;
|
||||||
use serde_derive::{Deserialize, Serialize};
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
use crate::passwords::Password;
|
||||||
|
|
||||||
/// The configuration struct containing all the configuration options.
|
/// The configuration struct containing all the configuration options.
|
||||||
#[derive(Serialize, Deserialize, Default)]
|
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||||
pub struct Configuration {
|
pub struct Configuration {
|
||||||
pub stored_username: String,
|
pub stored_username: Option<BareJid>,
|
||||||
pub stored_default_nick: String,
|
pub stored_default_nick: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cache the login attempt into the config and keyring.
|
/// Cache the login attempt into the config and keyring.
|
||||||
@ -36,30 +38,25 @@ pub struct Configuration {
|
|||||||
/// # Arguments
|
/// # Arguments
|
||||||
/// * `attempt` - The login attempt to store.
|
/// * `attempt` - The login attempt to store.
|
||||||
/// * `config` - The current app configuration; UseState should prevent race conditions in writing.
|
/// * `config` - The current app configuration; UseState should prevent race conditions in writing.
|
||||||
pub fn store_login_details(attempt: LoginAttempt, config: &mut UseRef<Configuration>) {
|
pub fn store_login_details(jid: BareJid,
|
||||||
// Extract the fields from the login attempt.
|
default_nick: String,
|
||||||
let LoginAttempt {
|
password: &Password,
|
||||||
username,
|
c: &mut Configuration) {
|
||||||
default_nick,
|
|
||||||
password,
|
|
||||||
} = attempt;
|
|
||||||
|
|
||||||
// Open the config state and store the username and default nick.
|
// Open the config state and store the username and default nick.
|
||||||
config.with_mut(|c| {
|
|
||||||
// Open the keyring and store the password.
|
// 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
|
entry
|
||||||
.set_password(&password)
|
.set_password(&password.0)
|
||||||
.expect("Failed to set password in keyring.");
|
.expect("Failed to set password in keyring.");
|
||||||
|
|
||||||
// Store the username and default nick in the config.
|
// Store the username and default nick in the config.
|
||||||
c.stored_username = username;
|
c.stored_username = Some(jid);
|
||||||
c.stored_default_nick = default_nick;
|
c.stored_default_nick = Some(default_nick);
|
||||||
|
|
||||||
if let Err(e) = confy::store("dergchat", None, c) {
|
if let Err(e) = confy::store("dergchat", None, c) {
|
||||||
error!("Failed to store the config file: {}", e);
|
error!("Failed to store the config file: {}", e);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load the configuration from the config file.
|
/// Load the configuration from the config file.
|
||||||
|
@ -20,6 +20,7 @@ mod configuration;
|
|||||||
mod types;
|
mod types;
|
||||||
mod widgets;
|
mod widgets;
|
||||||
mod xmpp_interface;
|
mod xmpp_interface;
|
||||||
|
mod passwords;
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_desktop::Config;
|
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.");
|
||||||
|
}
|
14
src/types.rs
14
src/types.rs
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
use jid::BareJid;
|
use jid::BareJid;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
use crate::passwords::Password;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct Room {
|
pub struct Room {
|
||||||
@ -28,18 +29,9 @@ pub struct Message {
|
|||||||
pub body: String,
|
pub body: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct LoginCredentials {
|
pub struct LoginCredentials {
|
||||||
pub username: BareJid,
|
pub username: BareJid,
|
||||||
pub default_nick: String,
|
pub default_nick: String,
|
||||||
pub password: String,
|
pub password: Password,
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -22,6 +22,7 @@ use crate::widgets::room_view::RoomView;
|
|||||||
use crate::widgets::sidebar::SideBar;
|
use crate::widgets::sidebar::SideBar;
|
||||||
use crate::xmpp_interface::NetworkCommand;
|
use crate::xmpp_interface::NetworkCommand;
|
||||||
use dioxus::core::{Element, Scope};
|
use dioxus::core::{Element, Scope};
|
||||||
|
use crate::passwords::Password;
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
@ -29,21 +30,42 @@ use jid::BareJid;
|
|||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::configuration::load_config;
|
use crate::configuration::{Configuration, load_config};
|
||||||
use crate::xmpp_interface;
|
use crate::xmpp_interface;
|
||||||
use keyring::Entry;
|
use keyring::Entry;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::string::String;
|
use std::string::String;
|
||||||
|
use crate::passwords::try_retrieve_password_from_keyring;
|
||||||
|
use crate::configuration::store_login_details;
|
||||||
|
|
||||||
pub mod login_screen;
|
pub mod login_screen;
|
||||||
mod no_room_open;
|
pub mod no_room_open;
|
||||||
pub mod room_join_widget;
|
pub mod room_join_widget;
|
||||||
pub mod room_list;
|
pub mod room_list;
|
||||||
pub mod room_view;
|
pub mod room_view;
|
||||||
pub mod send_message;
|
pub mod send_message;
|
||||||
pub mod sidebar;
|
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 {
|
pub fn App(cx: Scope) -> Element {
|
||||||
|
|
||||||
let messages = use_ref(cx, || HashMap::new());
|
let messages = use_ref(cx, || HashMap::new());
|
||||||
let current_room = use_state(cx, || None::<BareJid>);
|
let current_room = use_state(cx, || None::<BareJid>);
|
||||||
let connection_status = use_state(cx, || LoginStatus::LoggedOut);
|
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 = use_ref(cx, load_config);
|
||||||
|
|
||||||
{
|
use_on_create(cx, || {
|
||||||
let config = config.to_owned();
|
let config = config.to_owned();
|
||||||
let _connection_status = connection_status.to_owned();
|
let _connection_status = connection_status.to_owned();
|
||||||
let coroutine = coroutine.to_owned();
|
let coroutine = coroutine.to_owned();
|
||||||
|
|
||||||
use_on_create(cx, move || {
|
|
||||||
async move {
|
async move {
|
||||||
let entry = Entry::new("dergchat", &config.read().stored_username)
|
if let Some(credentials) = try_retrieve_credentials(&*config.read()) {
|
||||||
.expect("Failed to create keyring entry.");
|
info!("Will try to log in as {} with default nick {}", credentials.username, credentials.default_nick);
|
||||||
|
|
||||||
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 });
|
coroutine.send(NetworkCommand::TryLogin { credentials });
|
||||||
|
} else {
|
||||||
|
info!("No stored credentials found; will not try to automatically log in.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => println!("Failed to get password from keyring: {}", e),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
render! {
|
render! {
|
||||||
|
|
||||||
@ -97,33 +97,36 @@ pub fn App(cx: Scope) -> Element {
|
|||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
LoginScreen {
|
LoginScreen {
|
||||||
cached_username: config.read().stored_username.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(),
|
cached_nick: config.read().stored_default_nick.clone().unwrap_or("".to_string()),
|
||||||
login_state: connection_status.current().as_ref().clone(),
|
login_state: connection_status.current().as_ref().clone(),
|
||||||
on_login_attempt: move |x: LoginAttempt| {
|
on_login_attempt: move |x: LoginAttempt| {
|
||||||
|
|
||||||
{
|
let LoginAttempt { username, default_nick, password } = x;
|
||||||
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();
|
|
||||||
|
|
||||||
// Store the password in the keyring.
|
// First, validate the username as a jid:
|
||||||
let entry = Entry::new("dergchat", &x.username).expect("Failed to create keyring entry.");
|
let jid = match BareJid::from_str(&username) {
|
||||||
entry.set_password(&x.password).expect("Failed to set password in keyring.");
|
Ok(x) => x,
|
||||||
|
Err(e) => {
|
||||||
if let Err(e) = confy::store("dergchat", None, &*config.read()) {
|
error!("Invalid JID: {}", e);
|
||||||
error!("Failed to store JID in config file: {}", e);
|
return;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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 {
|
coroutine.send(NetworkCommand::TryLogin {
|
||||||
credentials: LoginCredentials {
|
credentials: LoginCredentials {
|
||||||
username: BareJid::from_str(&x.username).expect("Invalid JID"),
|
username: jid,
|
||||||
default_nick: x.default_nick,
|
default_nick,
|
||||||
password: x.password,
|
password,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -203,7 +203,7 @@ pub async fn run_xmpp_toplevel(
|
|||||||
|
|
||||||
connection_status.set(LoginStatus::LoggingIn);
|
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")
|
.set_client(ClientType::Pc, "dergchat")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user