Improve login ux and session handling

This commit is contained in:
Amanda Graven 2020-11-24 21:49:06 +01:00
parent a9c1d6ed01
commit 80f4ab6451
Signed by: amanda
GPG key ID: 45C461CDC9286390
2 changed files with 119 additions and 77 deletions

View file

@ -1,6 +1,5 @@
use matrix_sdk::{ use matrix_sdk::{
identifiers::DeviceId, identifiers::UserId, reqwest::Url, Client, ClientConfig, Session, identifiers::DeviceId, identifiers::UserId, reqwest::Url, Client, ClientConfig, SyncSettings,
SyncSettings,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -8,23 +7,14 @@ pub type Error = anyhow::Error;
// Needed to be able to serialize `Session`s. Should be done with serde remote. // Needed to be able to serialize `Session`s. Should be done with serde remote.
#[derive(Debug, Clone, Deserialize, Serialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SessionWrapper { pub struct Session {
access_token: String, access_token: String,
user_id: UserId, user_id: UserId,
device_id: Box<DeviceId>, device_id: Box<DeviceId>,
homeserver: String,
} }
impl From<SessionWrapper> for Session { impl From<Session> for matrix_sdk::Session {
fn from(s: SessionWrapper) -> Self {
Self {
access_token: s.access_token,
user_id: s.user_id,
device_id: s.device_id,
}
}
}
impl From<Session> for SessionWrapper {
fn from(s: Session) -> Self { fn from(s: Session) -> Self {
Self { Self {
access_token: s.access_token, access_token: s.access_token,
@ -34,38 +24,51 @@ impl From<Session> for SessionWrapper {
} }
} }
/// Login with credentials, creating a new authentication session
pub async fn login( pub async fn login(
username: &str, username: &str,
password: &str, password: &str,
server: &str, server: &str,
) -> Result<(Client, Session), Error> { ) -> Result<(Client, Session), Error> {
let url = Url::parse(server)?; let url = Url::parse(server)?;
let config = ClientConfig::new().store_path(&dirs::config_dir().unwrap().join("retrix")); let client = client(url)?;
let client = Client::new_with_config(url, config)?;
let session = match get_session()? {
Some(session) => {
client.restore_login(session.clone()).await?;
session
}
None => {
let response = client let response = client
.login(username, password, None, Some("retrix")) .login(
username,
password,
None,
Some(&format!("retrix@{}", hostname::get()?.to_string_lossy())),
)
.await?; .await?;
let session = Session { let session = Session {
access_token: response.access_token, access_token: response.access_token,
user_id: response.user_id, user_id: response.user_id,
device_id: response.device_id, device_id: response.device_id,
homeserver: server.to_owned(),
}; };
write_session(session.clone())?; write_session(&session)?;
session
}
};
client.sync_once(SyncSettings::new()).await?; client.sync_once(SyncSettings::new()).await?;
Ok((client, session)) Ok((client, session))
} }
pub async fn restore_login(session: Session) -> Result<(Client, Session), Error> {
let url = Url::parse(&session.homeserver)?;
let client = client(url)?;
client.restore_login(session.clone().into()).await?;
client.sync_once(SyncSettings::new()).await?;
Ok((client, session))
}
/// Create a matrix client handler with the desired configuration
fn client(url: Url) -> Result<Client, matrix_sdk::Error> {
let config = ClientConfig::new().store_path(&dirs::config_dir().unwrap().join("retrix"));
Client::new_with_config(url, config)
}
/// File path to store session data in /// File path to store session data in
fn session_path() -> std::path::PathBuf { fn session_path() -> std::path::PathBuf {
dirs::config_dir() dirs::config_dir()
@ -75,22 +78,19 @@ fn session_path() -> std::path::PathBuf {
} }
/// Read session data from config file /// Read session data from config file
fn get_session() -> Result<Option<Session>, Error> { pub fn get_session() -> Result<Option<Session>, Error> {
let path = session_path(); let path = session_path();
if !path.is_file() { if !path.is_file() {
return Ok(None); return Ok(None);
} }
let session: SessionWrapper = toml::from_slice(&std::fs::read(path)?)?; let session: Session = toml::from_slice(&std::fs::read(path)?)?;
Ok(Some(session.into())) Ok(Some(session))
} }
/// Save session data to config file /// Save session data to config file
fn write_session(session: Session) -> Result<(), Error> { fn write_session(session: &Session) -> Result<(), Error> {
let session: SessionWrapper = session.into();
let path = session_path();
let serialized = toml::to_string(&session)?; let serialized = toml::to_string(&session)?;
std::fs::write(path, serialized)?; std::fs::write(session_path(), serialized)?;
Ok(()) Ok(())
} }

114
src/ui.rs
View file

@ -1,6 +1,6 @@
use iced::{ use iced::{
text_input::{self, TextInput}, text_input::{self, TextInput},
Application, Button, Column, Command, Container, Element, Length, Row, Text, Application, Button, Column, Command, Container, Element, Length, Scrollable, Text,
}; };
use crate::matrix; use crate::matrix;
@ -18,35 +18,19 @@ pub enum Retrix {
server: String, server: String,
error: Option<String>, error: Option<String>,
}, },
AwaitLogin,
LoggedIn { LoggedIn {
client: matrix_sdk::Client, client: matrix_sdk::Client,
session: matrix_sdk::Session, session: matrix::Session,
rooms: Vec<matrix_sdk::Room>, rooms: Vec<String>,
room_scroll: iced::scrollable::State,
}, },
} }
#[derive(Debug, Clone)] impl Retrix {
pub enum Message { pub fn new_prompt() -> Retrix {
// Login form messages Retrix::Prompt {
SetUser(String),
SetPassword(String),
SetServer(String),
Login,
LoggedIn(matrix_sdk::Client, matrix_sdk::Session),
SetError(String),
// Main state messages
ResetRooms(Vec<matrix_sdk::Room>),
}
impl Application for Retrix {
type Message = Message;
type Executor = iced::executor::Default;
type Flags = ();
fn new(_flags: ()) -> (Self, Command<Self::Message>) {
let app = Retrix::Prompt {
user_input: text_input::State::new(), user_input: text_input::State::new(),
password_input: text_input::State::new(), password_input: text_input::State::new(),
server_input: text_input::State::new(), server_input: text_input::State::new(),
@ -56,8 +40,44 @@ impl Application for Retrix {
password: String::new(), password: String::new(),
server: String::new(), server: String::new(),
error: None, error: None,
}; }
(app, Command::none()) }
}
#[derive(Debug, Clone)]
pub enum Message {
// Login form messages
SetUser(String),
SetPassword(String),
SetServer(String),
Login,
LoggedIn(matrix_sdk::Client, matrix::Session),
LoginFailed(String),
// Main state messages
ResetRooms(Vec<String>),
}
impl Application for Retrix {
type Message = Message;
type Executor = iced::executor::Default;
type Flags = ();
fn new(_flags: ()) -> (Self, Command<Self::Message>) {
// Skip login prompt if we have a session saved
match matrix::get_session().ok().flatten() {
Some(session) => {
let command = Command::perform(
async move { matrix::restore_login(session).await },
|result| match result {
Ok((s, c)) => Message::LoggedIn(s, c),
Err(e) => Message::LoginFailed(e.to_string()),
},
);
(Retrix::AwaitLogin, command)
}
None => (Retrix::new_prompt(), Command::none()),
}
} }
fn title(&self) -> String { fn title(&self) -> String {
@ -70,38 +90,47 @@ impl Application for Retrix {
ref mut user, ref mut user,
ref mut password, ref mut password,
ref mut server, ref mut server,
ref mut error,
.. ..
} => match message { } => match message {
Message::SetUser(u) => *user = u, Message::SetUser(u) => *user = u,
Message::SetPassword(p) => *password = p, Message::SetPassword(p) => *password = p,
Message::SetServer(s) => *server = s, Message::SetServer(s) => *server = s,
Message::SetError(e) => *error = Some(e),
Message::Login => { Message::Login => {
let user = user.clone(); let user = user.clone();
let password = password.clone(); let password = password.clone();
let server = server.clone(); let server = server.clone();
*self = Retrix::AwaitLogin;
return Command::perform( return Command::perform(
async move { matrix::login(&user, &password, &server).await }, async move { matrix::login(&user, &password, &server).await },
|result| match result { |result| match result {
Ok((c, r)) => Message::LoggedIn(c, r), Ok((c, r)) => Message::LoggedIn(c, r),
Err(e) => Message::SetError(e.to_string()), Err(e) => Message::LoginFailed(e.to_string()),
}, },
); );
} }
_ => (),
},
Retrix::AwaitLogin => match message {
Message::LoginFailed(e) => {
*self = Retrix::new_prompt();
if let Retrix::Prompt { ref mut error, .. } = *self {
*error = Some(e);
}
}
Message::LoggedIn(client, session) => { Message::LoggedIn(client, session) => {
*self = Retrix::LoggedIn { *self = Retrix::LoggedIn {
client: client.clone(), client: client.clone(),
session, session,
rooms: Vec::new(), rooms: Vec::new(),
room_scroll: Default::default(),
}; };
let client = client.clone(); let client = client.clone();
Command::perform( return Command::perform(
async move { async move {
let mut list = Vec::new(); let mut list = Vec::new();
for (id, room) in client.joined_rooms().read().await.iter() { for (_, room) in client.joined_rooms().read().await.iter() {
let room = room.read().await; let name = room.read().await.display_name();
list.push(room.clone()); list.push(name);
} }
list list
}, },
@ -165,11 +194,24 @@ impl Application for Retrix {
.height(iced::Length::Fill) .height(iced::Length::Fill)
.into() .into()
} }
Retrix::LoggedIn { ref rooms, .. } => { Retrix::AwaitLogin => Container::new(Text::new("Logging in..."))
.center_x()
.center_y()
.width(Length::Fill)
.height(Length::Fill)
.into(),
Retrix::LoggedIn {
ref rooms,
ref mut room_scroll,
..
} => {
//let mut root_row = Row::new().width(Length::Fill).height(Length::Fill); //let mut root_row = Row::new().width(Length::Fill).height(Length::Fill);
let mut room_col = Column::new().width(400.into()).height(Length::Fill); let mut room_col = Scrollable::new(room_scroll)
.width(400.into())
.height(Length::Fill)
.spacing(15);
for room in rooms { for room in rooms {
room_col = room_col.push(Text::new(room.display_name())); room_col = room_col.push(Text::new(room));
} }
room_col.into() room_col.into()
//root_row = root_row.push(room_col); //root_row = root_row.push(room_col);