implement credential saving with cross-platform keyring
This commit is contained in:
parent
66e12f7264
commit
a938d39dc5
|
@ -2340,8 +2340,10 @@ dependencies = [
|
|||
"keyring",
|
||||
"luz",
|
||||
"serde",
|
||||
"thiserror 2.0.11",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"toml",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"uuid",
|
||||
|
|
|
@ -16,3 +16,5 @@ keyring = { version = "3", features = ["apple-native", "windows-native", "sync-s
|
|||
uuid = { version = "1.13.1", features = ["v4"] }
|
||||
indexmap = "2.7.1"
|
||||
serde = { version = "1.0.218", features = ["derive"] }
|
||||
thiserror = "2.0.11"
|
||||
toml = "0.8"
|
||||
|
|
|
@ -4,10 +4,12 @@ use iced::{
|
|||
Element, Task,
|
||||
};
|
||||
use jid::JID;
|
||||
use keyring::Entry;
|
||||
use luz::{
|
||||
presence::{Offline, Presence},
|
||||
LuzHandle,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio_stream::wrappers::ReceiverStream;
|
||||
use tracing::info;
|
||||
|
||||
|
@ -41,6 +43,12 @@ pub enum Action {
|
|||
ClientCreated(Task<crate::Message>),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Creds {
|
||||
pub jid: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
impl LoginModal {
|
||||
pub fn update(&mut self, message: Message) -> Action {
|
||||
match message {
|
||||
|
@ -60,6 +68,7 @@ impl LoginModal {
|
|||
info!("submitting login");
|
||||
let jid_str = self.jid.clone();
|
||||
let password = self.password.clone();
|
||||
let remember_me = self.remember_me.clone();
|
||||
Action::ClientCreated(
|
||||
Task::future(async move {
|
||||
let jid: Result<JID, _> = jid_str.parse();
|
||||
|
@ -70,19 +79,56 @@ impl LoginModal {
|
|||
.await;
|
||||
match result {
|
||||
Ok((luz_handle, receiver)) => {
|
||||
let stream = ReceiverStream::new(receiver);
|
||||
let stream =
|
||||
stream.map(|message| crate::Message::Luz(message));
|
||||
vec![
|
||||
Task::done(crate::Message::ClientCreated(Client {
|
||||
let mut tasks = Vec::new();
|
||||
tasks.push(Task::done(crate::Message::ClientCreated(
|
||||
Client {
|
||||
client: luz_handle,
|
||||
jid: j,
|
||||
connection_status: Presence::Offline(
|
||||
Offline::default(),
|
||||
),
|
||||
})),
|
||||
Task::stream(stream),
|
||||
]
|
||||
},
|
||||
)));
|
||||
let stream = ReceiverStream::new(receiver);
|
||||
let stream =
|
||||
stream.map(|message| crate::Message::Luz(message));
|
||||
tasks.push(Task::stream(stream));
|
||||
|
||||
if remember_me {
|
||||
let entry = Entry::new("macaw", "macaw");
|
||||
match entry {
|
||||
Ok(e) => {
|
||||
let creds = Creds {
|
||||
jid: jid_str,
|
||||
password,
|
||||
};
|
||||
let creds = toml::to_string(&creds);
|
||||
match creds {
|
||||
Ok(c) => {
|
||||
let result = e.set_password(&c);
|
||||
if let Err(e) = result {
|
||||
tasks.push(Task::done(crate::Message::Error(
|
||||
crate::Error::CredentialsSave(e.into()),
|
||||
)));
|
||||
}
|
||||
}
|
||||
Err(e) => tasks.push(Task::done(
|
||||
crate::Message::Error(
|
||||
crate::Error::CredentialsSave(
|
||||
e.into(),
|
||||
),
|
||||
),
|
||||
)),
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
tasks.push(Task::done(crate::Message::Error(
|
||||
crate::Error::CredentialsSave(e.into()),
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
tasks
|
||||
}
|
||||
Err(_e) => {
|
||||
tracing::error!("error (database probably)");
|
||||
|
|
113
src/main.rs
113
src/main.rs
|
@ -2,6 +2,7 @@ use std::borrow::Cow;
|
|||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt::Debug;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::sync::Arc;
|
||||
|
||||
use iced::futures::{SinkExt, Stream, StreamExt};
|
||||
use iced::widget::button::Status;
|
||||
|
@ -15,12 +16,13 @@ use iced::{stream, Color, Element, Subscription, Task, Theme};
|
|||
use indexmap::{indexmap, IndexMap};
|
||||
use jid::JID;
|
||||
use keyring::Entry;
|
||||
use login_modal::LoginModal;
|
||||
use login_modal::{Creds, LoginModal};
|
||||
use luz::chat::{Chat, Message as ChatMessage};
|
||||
use luz::presence::{Offline, Presence};
|
||||
use luz::CommandMessage;
|
||||
use luz::{roster::Contact, user::User, LuzHandle, UpdateMessage};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
use tokio_stream::wrappers::ReceiverStream;
|
||||
use tracing::{error, info};
|
||||
|
@ -58,11 +60,6 @@ pub struct OpenChat {
|
|||
|
||||
pub struct NewChat;
|
||||
|
||||
pub struct Creds {
|
||||
jid: String,
|
||||
password: String,
|
||||
}
|
||||
|
||||
impl Macaw {
|
||||
pub fn new(client: Option<Client>, config: Config) -> Self {
|
||||
let account;
|
||||
|
@ -112,11 +109,60 @@ impl Deref for Client {
|
|||
}
|
||||
}
|
||||
|
||||
fn main() -> iced::Result {
|
||||
#[tokio::main]
|
||||
async fn main() -> iced::Result {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let cfg = confy::load("macaw", None).unwrap();
|
||||
let client: Option<(JID, LuzHandle, mpsc::Receiver<UpdateMessage>)> = None;
|
||||
let entry = Entry::new("macaw", "macaw");
|
||||
let mut client_creation_error: Option<Error> = None;
|
||||
let mut creds: Option<Creds> = None;
|
||||
|
||||
match entry {
|
||||
Ok(e) => {
|
||||
let result = e.get_password();
|
||||
match result {
|
||||
Ok(c) => {
|
||||
let result = toml::from_str(&c);
|
||||
match result {
|
||||
Ok(c) => creds = Some(c),
|
||||
Err(e) => {
|
||||
client_creation_error =
|
||||
Some(Error::CredentialsLoad(CredentialsLoadError::Toml(e.into())))
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => match e {
|
||||
keyring::Error::NoEntry => {}
|
||||
_ => {
|
||||
client_creation_error = Some(Error::CredentialsLoad(
|
||||
CredentialsLoadError::Keyring(e.into()),
|
||||
))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
client_creation_error = Some(Error::CredentialsLoad(CredentialsLoadError::Keyring(
|
||||
e.into(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
let mut client: Option<(JID, LuzHandle, mpsc::Receiver<UpdateMessage>)> = None;
|
||||
if let Some(creds) = creds {
|
||||
let jid = creds.jid.parse::<JID>();
|
||||
match jid {
|
||||
Ok(jid) => {
|
||||
let luz = LuzHandle::new(jid.clone(), creds.password.to_string(), "macaw.db").await;
|
||||
match luz {
|
||||
Ok((handle, recv)) => client = Some((jid.as_bare(), handle, recv)),
|
||||
Err(e) => client_creation_error = Some(Error::ClientCreation(e)),
|
||||
}
|
||||
}
|
||||
Err(e) => client_creation_error = Some(Error::CredentialsLoad(e.into())),
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((jid, luz_handle, update_recv)) = client {
|
||||
let stream = ReceiverStream::new(update_recv);
|
||||
|
@ -137,8 +183,13 @@ fn main() -> iced::Result {
|
|||
)
|
||||
})
|
||||
} else {
|
||||
iced::application("Macaw", Macaw::update, Macaw::view)
|
||||
.run_with(|| (Macaw::new(None, cfg), Task::none()))
|
||||
if let Some(e) = client_creation_error {
|
||||
iced::application("Macaw", Macaw::update, Macaw::view)
|
||||
.run_with(|| (Macaw::new(None, cfg), Task::done(Message::Error(e))))
|
||||
} else {
|
||||
iced::application("Macaw", Macaw::update, Macaw::view)
|
||||
.run_with(|| (Macaw::new(None, cfg), Task::none()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -156,6 +207,47 @@ pub enum Message {
|
|||
CloseChat(JID),
|
||||
MessageCompose(String),
|
||||
SendMessage(JID, String),
|
||||
Error(Error),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error, Clone)]
|
||||
pub enum Error {
|
||||
#[error("failed to create Luz client: {0}")]
|
||||
ClientCreation(#[from] luz::error::DatabaseError),
|
||||
#[error("failed to save credentials: {0}")]
|
||||
CredentialsSave(CredentialsSaveError),
|
||||
#[error("failed to load credentials: {0}")]
|
||||
CredentialsLoad(CredentialsLoadError),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error, Clone)]
|
||||
pub enum CredentialsSaveError {
|
||||
#[error("keyring: {0}")]
|
||||
Keyring(Arc<keyring::Error>),
|
||||
#[error("toml serialisation: {0}")]
|
||||
Toml(#[from] toml::ser::Error),
|
||||
}
|
||||
|
||||
impl From<keyring::Error> for CredentialsSaveError {
|
||||
fn from(e: keyring::Error) -> Self {
|
||||
Self::Keyring(Arc::new(e))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error, Clone)]
|
||||
pub enum CredentialsLoadError {
|
||||
#[error("keyring: {0}")]
|
||||
Keyring(Arc<keyring::Error>),
|
||||
#[error("toml serialisation: {0}")]
|
||||
Toml(#[from] toml::de::Error),
|
||||
#[error("invalid jid: {0}")]
|
||||
JID(#[from] jid::ParseError),
|
||||
}
|
||||
|
||||
impl From<keyring::Error> for CredentialsLoadError {
|
||||
fn from(e: keyring::Error) -> Self {
|
||||
Self::Keyring(Arc::new(e))
|
||||
}
|
||||
}
|
||||
|
||||
impl Macaw {
|
||||
|
@ -358,6 +450,7 @@ impl Macaw {
|
|||
)
|
||||
.discard()
|
||||
}
|
||||
Message::Error(error) => todo!("error notification toasts, logging?"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue