Add password authentication dialog
This commit is contained in:
parent
19d75a1d15
commit
7b52add24e
3 changed files with 148 additions and 43 deletions
|
|
@ -15,6 +15,7 @@ Procedure when bumping the version number:
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
- Authentication dialog for password-protected rooms
|
||||||
- Error popups in rooms when something goes wrong
|
- Error popups in rooms when something goes wrong
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use std::time::Duration;
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use cookie::{Cookie, CookieJar};
|
use cookie::{Cookie, CookieJar};
|
||||||
use euphoxide::api::packet::ParsedPacket;
|
use euphoxide::api::packet::ParsedPacket;
|
||||||
use euphoxide::api::{Data, Log, Nick, Send, Snowflake, Time, UserId};
|
use euphoxide::api::{Auth, AuthOption, Data, Log, Nick, Send, Snowflake, Time, UserId};
|
||||||
use euphoxide::conn::{ConnRx, ConnTx, Joining, Status};
|
use euphoxide::conn::{ConnRx, ConnTx, Joining, Status};
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
|
@ -46,6 +46,7 @@ enum Event {
|
||||||
// Commands
|
// Commands
|
||||||
Status(oneshot::Sender<Option<Status>>),
|
Status(oneshot::Sender<Option<Status>>),
|
||||||
RequestLogs,
|
RequestLogs,
|
||||||
|
Auth(String),
|
||||||
Nick(String),
|
Nick(String),
|
||||||
Send(Option<Snowflake>, String, oneshot::Sender<Snowflake>),
|
Send(Option<Snowflake>, String, oneshot::Sender<Snowflake>),
|
||||||
}
|
}
|
||||||
|
|
@ -188,6 +189,7 @@ impl State {
|
||||||
}
|
}
|
||||||
Event::Status(reply_tx) => self.on_status(reply_tx).await,
|
Event::Status(reply_tx) => self.on_status(reply_tx).await,
|
||||||
Event::RequestLogs => self.on_request_logs(),
|
Event::RequestLogs => self.on_request_logs(),
|
||||||
|
Event::Auth(password) => self.on_auth(password),
|
||||||
Event::Nick(name) => self.on_nick(name),
|
Event::Nick(name) => self.on_nick(name),
|
||||||
Event::Send(parent, content, id_tx) => self.on_send(parent, content, id_tx),
|
Event::Send(parent, content, id_tx) => self.on_send(parent, content, id_tx),
|
||||||
}
|
}
|
||||||
|
|
@ -321,6 +323,20 @@ impl State {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_auth(&self, password: String) {
|
||||||
|
if let Some(conn_tx) = &self.conn_tx {
|
||||||
|
let conn_tx = conn_tx.clone();
|
||||||
|
task::spawn(async move {
|
||||||
|
let _ = conn_tx
|
||||||
|
.send(Auth {
|
||||||
|
r#type: AuthOption::Passcode,
|
||||||
|
passcode: Some(password),
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn on_nick(&self, name: String) {
|
fn on_nick(&self, name: String) {
|
||||||
if let Some(conn_tx) = &self.conn_tx {
|
if let Some(conn_tx) = &self.conn_tx {
|
||||||
let conn_tx = conn_tx.clone();
|
let conn_tx = conn_tx.clone();
|
||||||
|
|
@ -389,6 +405,12 @@ impl Room {
|
||||||
rx.await.map_err(|_| Error::Stopped)
|
rx.await.map_err(|_| Error::Stopped)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn auth(&self, password: String) -> Result<(), Error> {
|
||||||
|
self.event_tx
|
||||||
|
.send(Event::Auth(password))
|
||||||
|
.map_err(|_| Error::Stopped)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn nick(&self, name: String) -> Result<(), Error> {
|
pub fn nick(&self, name: String) -> Result<(), Error> {
|
||||||
self.event_tx
|
self.event_tx
|
||||||
.send(Event::Nick(name))
|
.send(Event::Nick(name))
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use std::sync::Arc;
|
||||||
use crossterm::event::KeyCode;
|
use crossterm::event::KeyCode;
|
||||||
use crossterm::style::{Color, ContentStyle, Stylize};
|
use crossterm::style::{Color, ContentStyle, Stylize};
|
||||||
use euphoxide::api::{Data, PacketType, SessionType, SessionView, Snowflake};
|
use euphoxide::api::{Data, PacketType, SessionType, SessionView, Snowflake};
|
||||||
use euphoxide::conn::{Joined, Status};
|
use euphoxide::conn::{Joined, Joining, Status};
|
||||||
use parking_lot::FairMutex;
|
use parking_lot::FairMutex;
|
||||||
use tokio::sync::oneshot::error::TryRecvError;
|
use tokio::sync::oneshot::error::TryRecvError;
|
||||||
use tokio::sync::{mpsc, oneshot};
|
use tokio::sync::{mpsc, oneshot};
|
||||||
|
|
@ -35,7 +35,8 @@ use super::popup::RoomPopup;
|
||||||
|
|
||||||
enum State {
|
enum State {
|
||||||
Normal,
|
Normal,
|
||||||
ChooseNick(EditorState),
|
Auth(EditorState),
|
||||||
|
Nick(EditorState),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::large_enum_variant)]
|
#[allow(clippy::large_enum_variant)]
|
||||||
|
|
@ -153,10 +154,35 @@ impl EuphRoom {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn widget(&mut self) -> BoxedWidget {
|
fn stabilize_state(&mut self, status: &RoomStatus) {
|
||||||
self.stabilize_pseudo_msg().await;
|
match &self.state {
|
||||||
|
State::Auth(_)
|
||||||
|
if !matches!(
|
||||||
|
status,
|
||||||
|
RoomStatus::Connected(Status::Joining(Joining {
|
||||||
|
bounce: Some(_),
|
||||||
|
..
|
||||||
|
}))
|
||||||
|
) =>
|
||||||
|
{
|
||||||
|
self.state = State::Normal
|
||||||
|
}
|
||||||
|
State::Nick(_) if !matches!(status, RoomStatus::Connected(Status::Joined(_))) => {
|
||||||
|
self.state = State::Normal
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn stabilize(&mut self, status: &RoomStatus) {
|
||||||
|
self.stabilize_pseudo_msg().await;
|
||||||
|
self.stabilize_state(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn widget(&mut self) -> BoxedWidget {
|
||||||
let status = self.status().await;
|
let status = self.status().await;
|
||||||
|
self.stabilize(&status).await;
|
||||||
|
|
||||||
let chat = if let RoomStatus::Connected(Status::Joined(joined)) = &status {
|
let chat = if let RoomStatus::Connected(Status::Joined(joined)) = &status {
|
||||||
self.widget_with_nick_list(&status, joined).await
|
self.widget_with_nick_list(&status, joined).await
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -167,7 +193,8 @@ impl EuphRoom {
|
||||||
|
|
||||||
match &self.state {
|
match &self.state {
|
||||||
State::Normal => {}
|
State::Normal => {}
|
||||||
State::ChooseNick(editor) => layers.push(Self::choose_nick_widget(editor)),
|
State::Auth(editor) => layers.push(Self::auth_widget(editor)),
|
||||||
|
State::Nick(editor) => layers.push(Self::nick_widget(editor)),
|
||||||
}
|
}
|
||||||
|
|
||||||
for popup in &self.popups {
|
for popup in &self.popups {
|
||||||
|
|
@ -177,7 +204,14 @@ impl EuphRoom {
|
||||||
Layer::new(layers).into()
|
Layer::new(layers).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn choose_nick_widget(editor: &EditorState) -> BoxedWidget {
|
fn auth_widget(editor: &EditorState) -> BoxedWidget {
|
||||||
|
Popup::new(Padding::new(editor.widget()).left(1))
|
||||||
|
.title("Enter password")
|
||||||
|
.inner_padding(false)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nick_widget(editor: &EditorState) -> BoxedWidget {
|
||||||
let editor = editor
|
let editor = editor
|
||||||
.widget()
|
.widget()
|
||||||
.highlight(|s| Styled::new(s, euph::nick_style(s)));
|
.highlight(|s| Styled::new(s, euph::nick_style(s)));
|
||||||
|
|
@ -373,14 +407,21 @@ impl EuphRoom {
|
||||||
|
|
||||||
match &self.state {
|
match &self.state {
|
||||||
State::Normal => {
|
State::Normal => {
|
||||||
// TODO Use if-let chain
|
|
||||||
bindings.binding("esc", "leave room");
|
bindings.binding("esc", "leave room");
|
||||||
|
|
||||||
let can_compose = if let Some(room) = &self.room {
|
let can_compose = if let Some(room) = &self.room {
|
||||||
if let Ok(Some(Status::Joined(_))) = room.status().await {
|
match room.status().await {
|
||||||
|
Ok(Some(Status::Joining(Joining {
|
||||||
|
bounce: Some(_), ..
|
||||||
|
}))) => {
|
||||||
|
bindings.binding("a", "authenticate");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
Ok(Some(Status::Joined(_))) => {
|
||||||
bindings.binding("n", "change nick");
|
bindings.binding("n", "change nick");
|
||||||
true
|
true
|
||||||
} else {
|
}
|
||||||
false
|
_ => false,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
|
|
@ -389,7 +430,12 @@ impl EuphRoom {
|
||||||
bindings.empty();
|
bindings.empty();
|
||||||
self.chat.list_key_bindings(bindings, can_compose).await;
|
self.chat.list_key_bindings(bindings, can_compose).await;
|
||||||
}
|
}
|
||||||
State::ChooseNick(_) => {
|
State::Auth(_) => {
|
||||||
|
bindings.binding("esc", "abort");
|
||||||
|
bindings.binding("enter", "authenticate");
|
||||||
|
util::list_editor_key_bindings(bindings, Self::nick_char, false);
|
||||||
|
}
|
||||||
|
State::Nick(_) => {
|
||||||
bindings.binding("esc", "abort");
|
bindings.binding("esc", "abort");
|
||||||
bindings.binding("enter", "set nick");
|
bindings.binding("enter", "set nick");
|
||||||
util::list_editor_key_bindings(bindings, Self::nick_char, false);
|
util::list_editor_key_bindings(bindings, Self::nick_char, false);
|
||||||
|
|
@ -413,12 +459,15 @@ impl EuphRoom {
|
||||||
|
|
||||||
match &self.state {
|
match &self.state {
|
||||||
State::Normal => {
|
State::Normal => {
|
||||||
// TODO Use if-let chain
|
|
||||||
if let Some(room) = &self.room {
|
if let Some(room) = &self.room {
|
||||||
if let Ok(Some(Status::Joined(joined))) = room.status().await {
|
let status = room.status().await;
|
||||||
|
let can_compose = matches!(status, Ok(Some(Status::Joined(_))));
|
||||||
|
|
||||||
|
// We need to handle chat input first, otherwise the other
|
||||||
|
// key bindings will shadow characters in the editor.
|
||||||
match self
|
match self
|
||||||
.chat
|
.chat
|
||||||
.handle_input_event(terminal, crossterm_lock, event, true)
|
.handle_input_event(terminal, crossterm_lock, event, can_compose)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Reaction::NotHandled => {}
|
Reaction::NotHandled => {}
|
||||||
|
|
@ -432,23 +481,56 @@ impl EuphRoom {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match status {
|
||||||
|
Ok(Some(Status::Joining(Joining {
|
||||||
|
bounce: Some(_), ..
|
||||||
|
}))) => {
|
||||||
|
if let key!('a') | key!('A') = event {
|
||||||
|
self.state = State::Auth(EditorState::new());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
Ok(Some(Status::Joined(joined))) => {
|
||||||
if let key!('n') | key!('N') = event {
|
if let key!('n') | key!('N') = event {
|
||||||
self.state = State::ChooseNick(EditorState::with_initial_text(
|
self.state = State::Nick(EditorState::with_initial_text(
|
||||||
joined.session.name.clone(),
|
joined.session.name,
|
||||||
));
|
));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
true
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
_ => false,
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
self.chat
|
self.chat
|
||||||
.handle_input_event(terminal, crossterm_lock, event, false)
|
.handle_input_event(terminal, crossterm_lock, event, false)
|
||||||
.await
|
.await
|
||||||
.handled()
|
.handled()
|
||||||
}
|
}
|
||||||
State::ChooseNick(ed) => match event {
|
}
|
||||||
|
State::Auth(ed) => match event {
|
||||||
|
key!(Esc) => {
|
||||||
|
self.state = State::Normal;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
key!(Enter) => {
|
||||||
|
if let Some(room) = &self.room {
|
||||||
|
let _ = room.auth(ed.text());
|
||||||
|
}
|
||||||
|
self.state = State::Normal;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => util::handle_editor_input_event(
|
||||||
|
ed,
|
||||||
|
terminal,
|
||||||
|
crossterm_lock,
|
||||||
|
event,
|
||||||
|
Self::nick_char,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
State::Nick(ed) => match event {
|
||||||
key!(Esc) => {
|
key!(Esc) => {
|
||||||
self.state = State::Normal;
|
self.state = State::Normal;
|
||||||
true
|
true
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue