feat: add setting for relay authentication (#133)
* remember auth relay * . * .
This commit is contained in:
@@ -520,10 +520,19 @@ impl ChatSpace {
|
||||
IngesterSignal::Auth(req) => {
|
||||
let relay_url = &req.url;
|
||||
let challenge = &req.challenge;
|
||||
let auth_auth = AppSettings::get_auto_auth(cx);
|
||||
let auth_relays = AppSettings::read_global(cx).auth_relays();
|
||||
|
||||
view.update(cx, |this, cx| {
|
||||
this.push_auth_request(challenge, relay_url, cx);
|
||||
this.open_auth_request(challenge, relay_url, window, cx);
|
||||
|
||||
if auth_auth && auth_relays.contains(relay_url) {
|
||||
// Automatically authenticate if the relay is authenticated before
|
||||
this.auth(challenge, relay_url, window, cx);
|
||||
} else {
|
||||
// Otherwise open the auth request popup
|
||||
this.open_auth_request(challenge, relay_url, window, cx);
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
@@ -783,6 +792,7 @@ impl ChatSpace {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let settings = AppSettings::global(cx);
|
||||
let challenge = challenge.to_string();
|
||||
let url = url.to_owned();
|
||||
let challenge_clone = challenge.clone();
|
||||
@@ -841,8 +851,15 @@ impl ChatSpace {
|
||||
cx.update(|window, cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.remove_auth_request(&challenge, cx);
|
||||
|
||||
// Save the authenticated relay to automatically authenticate future requests
|
||||
settings.update(cx, |this, cx| {
|
||||
this.push_auth_relay(url.clone(), cx);
|
||||
});
|
||||
|
||||
// Clear the current notification
|
||||
window.clear_notification_by_id(SharedString::from(challenge), cx);
|
||||
|
||||
// Push a new notification after current cycle
|
||||
cx.defer_in(window, move |_, window, cx| {
|
||||
window
|
||||
@@ -1039,7 +1056,7 @@ impl ChatSpace {
|
||||
window.open_modal(cx, move |modal, _window, _cx| {
|
||||
modal
|
||||
.title(shared_t!("common.preferences"))
|
||||
.width(px(480.))
|
||||
.width(px(580.))
|
||||
.child(view.clone())
|
||||
});
|
||||
}
|
||||
@@ -1201,6 +1218,7 @@ impl ChatSpace {
|
||||
cx: &mut Context<Self>,
|
||||
) -> impl IntoElement {
|
||||
let proxy = AppSettings::get_proxy_user_avatars(cx);
|
||||
let is_auto_auth = AppSettings::read_global(cx).is_auto_auth();
|
||||
let updating = AutoUpdater::read_global(cx).status.is_updating();
|
||||
let updated = AutoUpdater::read_global(cx).status.is_updated();
|
||||
let auth_requests = self.auth_requests.len();
|
||||
@@ -1239,7 +1257,7 @@ impl ChatSpace {
|
||||
}),
|
||||
)
|
||||
})
|
||||
.when(auth_requests > 0, |this| {
|
||||
.when(auth_requests > 0 && !is_auto_auth, |this| {
|
||||
this.child(
|
||||
h_flex()
|
||||
.id("requests")
|
||||
|
||||
@@ -4,22 +4,22 @@ use gpui::{
|
||||
div, px, relative, rems, App, AppContext, Context, Entity, InteractiveElement, IntoElement,
|
||||
ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, Window,
|
||||
};
|
||||
use i18n::t;
|
||||
use i18n::{shared_t, t};
|
||||
use nostr_sdk::prelude::*;
|
||||
use registry::Registry;
|
||||
use settings::AppSettings;
|
||||
use theme::ActiveTheme;
|
||||
use ui::avatar::Avatar;
|
||||
use ui::button::{Button, ButtonVariants};
|
||||
use ui::button::{Button, ButtonRounded, ButtonVariants};
|
||||
use ui::input::{InputState, TextInput};
|
||||
use ui::modal::ModalButtonProps;
|
||||
use ui::switch::Switch;
|
||||
use ui::{v_flex, ContextModal, IconName, Sizable, Size, StyledExt};
|
||||
use ui::{h_flex, v_flex, ContextModal, IconName, Sizable, Size, StyledExt};
|
||||
|
||||
use crate::views::{edit_profile, setup_relay};
|
||||
|
||||
pub fn init(window: &mut Window, cx: &mut App) -> Entity<Preferences> {
|
||||
Preferences::new(window, cx)
|
||||
cx.new(|cx| Preferences::new(window, cx))
|
||||
}
|
||||
|
||||
pub struct Preferences {
|
||||
@@ -27,17 +27,15 @@ pub struct Preferences {
|
||||
}
|
||||
|
||||
impl Preferences {
|
||||
pub fn new(window: &mut Window, cx: &mut App) -> Entity<Self> {
|
||||
cx.new(|cx| {
|
||||
let media_server = AppSettings::get_media_server(cx).to_string();
|
||||
let media_input = cx.new(|cx| {
|
||||
InputState::new(window, cx)
|
||||
.default_value(media_server.clone())
|
||||
.placeholder(media_server)
|
||||
});
|
||||
pub fn new(window: &mut Window, cx: &mut App) -> Self {
|
||||
let media_server = AppSettings::get_media_server(cx).to_string();
|
||||
let media_input = cx.new(|cx| {
|
||||
InputState::new(window, cx)
|
||||
.default_value(media_server.clone())
|
||||
.placeholder(media_server)
|
||||
});
|
||||
|
||||
Self { media_input }
|
||||
})
|
||||
Self { media_input }
|
||||
}
|
||||
|
||||
fn open_edit_profile(&self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
@@ -117,58 +115,50 @@ impl Render for Preferences {
|
||||
let input_state = self.media_input.downgrade();
|
||||
let profile = Registry::read_global(cx).identity(cx);
|
||||
|
||||
let backup_messages = AppSettings::get_backup_messages(cx);
|
||||
let auto_auth = AppSettings::get_auto_auth(cx);
|
||||
let backup = AppSettings::get_backup_messages(cx);
|
||||
let screening = AppSettings::get_screening(cx);
|
||||
let contact_bypass = AppSettings::get_contact_bypass(cx);
|
||||
let proxy_avatar = AppSettings::get_proxy_user_avatars(cx);
|
||||
let hide_avatar = AppSettings::get_hide_user_avatars(cx);
|
||||
let bypass = AppSettings::get_contact_bypass(cx);
|
||||
let proxy = AppSettings::get_proxy_user_avatars(cx);
|
||||
let hide = AppSettings::get_hide_user_avatars(cx);
|
||||
|
||||
v_flex()
|
||||
.child(
|
||||
v_flex()
|
||||
.py_2()
|
||||
.pb_2()
|
||||
.gap_2()
|
||||
.child(
|
||||
div()
|
||||
.text_sm()
|
||||
.text_color(cx.theme().text_placeholder)
|
||||
.font_semibold()
|
||||
.child(SharedString::new(t!("preferences.account_header"))),
|
||||
.child(shared_t!("preferences.account_header")),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
h_flex()
|
||||
.w_full()
|
||||
.flex()
|
||||
.justify_between()
|
||||
.items_center()
|
||||
.child(
|
||||
div()
|
||||
.id("current-user")
|
||||
.flex()
|
||||
.items_center()
|
||||
h_flex()
|
||||
.id("user")
|
||||
.gap_2()
|
||||
.child(
|
||||
Avatar::new(profile.avatar_url(proxy_avatar))
|
||||
.size(rems(2.4)),
|
||||
)
|
||||
.child(Avatar::new(profile.avatar_url(proxy)).size(rems(2.4)))
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.text_sm()
|
||||
.child(
|
||||
div()
|
||||
.line_height(relative(1.3))
|
||||
.font_semibold()
|
||||
.line_height(relative(1.3))
|
||||
.child(profile.display_name()),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.line_height(relative(1.3))
|
||||
.text_xs()
|
||||
.text_color(cx.theme().text_muted)
|
||||
.child(SharedString::new(t!(
|
||||
"preferences.see_your_profile"
|
||||
))),
|
||||
.line_height(relative(1.3))
|
||||
.child(shared_t!("preferences.account_btn")),
|
||||
),
|
||||
)
|
||||
.on_click(cx.listener(move |this, _e, window, cx| {
|
||||
@@ -178,8 +168,9 @@ impl Render for Preferences {
|
||||
.child(
|
||||
Button::new("relays")
|
||||
.label("Messaging Relays")
|
||||
.ghost()
|
||||
.small()
|
||||
.xsmall()
|
||||
.ghost_alt()
|
||||
.rounded(ButtonRounded::Full)
|
||||
.on_click(cx.listener(move |this, _e, window, cx| {
|
||||
this.open_relays(window, cx);
|
||||
})),
|
||||
@@ -196,39 +187,48 @@ impl Render for Preferences {
|
||||
.text_sm()
|
||||
.text_color(cx.theme().text_placeholder)
|
||||
.font_semibold()
|
||||
.child(SharedString::new(t!("preferences.media_server_header"))),
|
||||
.child(shared_t!("preferences.relay_and_media")),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
v_flex()
|
||||
.my_1()
|
||||
.flex()
|
||||
.items_start()
|
||||
.gap_1()
|
||||
.child(TextInput::new(&self.media_input).xsmall())
|
||||
.child(
|
||||
Button::new("update")
|
||||
.icon(IconName::CheckCircleFill)
|
||||
.ghost()
|
||||
.with_size(Size::Size(px(26.)))
|
||||
.on_click(move |_, window, cx| {
|
||||
if let Some(input) = input_state.upgrade() {
|
||||
let Ok(url) = Url::parse(input.read(cx).value()) else {
|
||||
window.push_notification(
|
||||
t!("preferences.url_not_valid"),
|
||||
cx,
|
||||
);
|
||||
return;
|
||||
};
|
||||
AppSettings::update_media_server(url, cx);
|
||||
}
|
||||
}),
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(TextInput::new(&self.media_input).xsmall())
|
||||
.child(
|
||||
Button::new("update")
|
||||
.icon(IconName::Check)
|
||||
.ghost()
|
||||
.with_size(Size::Size(px(26.)))
|
||||
.on_click(move |_, _window, cx| {
|
||||
if let Some(input) = input_state.upgrade() {
|
||||
let Ok(url) =
|
||||
Url::parse(input.read(cx).value())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
AppSettings::update_media_server(url, cx);
|
||||
}
|
||||
}),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(cx.theme().text_muted)
|
||||
.child(shared_t!("preferences.media_description")),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(cx.theme().text_muted)
|
||||
.child(SharedString::new(t!("preferences.media_description"))),
|
||||
Switch::new("auth")
|
||||
.label(t!("preferences.auto_auth"))
|
||||
.description(t!("preferences.auto_auth_description"))
|
||||
.checked(auto_auth)
|
||||
.on_click(move |_, _window, cx| {
|
||||
AppSettings::update_auto_auth(!auto_auth, cx);
|
||||
}),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
@@ -242,7 +242,7 @@ impl Render for Preferences {
|
||||
.text_sm()
|
||||
.text_color(cx.theme().text_placeholder)
|
||||
.font_semibold()
|
||||
.child(SharedString::new(t!("preferences.messages_header"))),
|
||||
.child(shared_t!("preferences.messages_header")),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
@@ -260,18 +260,18 @@ impl Render for Preferences {
|
||||
Switch::new("bypass")
|
||||
.label(t!("preferences.bypass_label"))
|
||||
.description(t!("preferences.bypass_description"))
|
||||
.checked(contact_bypass)
|
||||
.checked(bypass)
|
||||
.on_click(move |_, _window, cx| {
|
||||
AppSettings::update_contact_bypass(!contact_bypass, cx);
|
||||
AppSettings::update_contact_bypass(!bypass, cx);
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
Switch::new("backup_messages")
|
||||
.label(t!("preferences.backup_messages_label"))
|
||||
Switch::new("backup")
|
||||
.label(t!("preferences.backup_label"))
|
||||
.description(t!("preferences.backup_description"))
|
||||
.checked(backup_messages)
|
||||
.checked(backup)
|
||||
.on_click(move |_, _window, cx| {
|
||||
AppSettings::update_backup_messages(!backup_messages, cx);
|
||||
AppSettings::update_backup_messages(!backup, cx);
|
||||
}),
|
||||
),
|
||||
),
|
||||
@@ -287,27 +287,27 @@ impl Render for Preferences {
|
||||
.text_sm()
|
||||
.text_color(cx.theme().text_placeholder)
|
||||
.font_semibold()
|
||||
.child(SharedString::new(t!("preferences.display_header"))),
|
||||
.child(shared_t!("preferences.display_header")),
|
||||
)
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
Switch::new("hide_user_avatars")
|
||||
Switch::new("hide_avatar")
|
||||
.label(t!("preferences.hide_avatars_label"))
|
||||
.description(t!("preferences.hide_avatar_description"))
|
||||
.checked(hide_avatar)
|
||||
.checked(hide)
|
||||
.on_click(move |_, _window, cx| {
|
||||
AppSettings::update_hide_user_avatars(!hide_avatar, cx);
|
||||
AppSettings::update_hide_user_avatars(!hide, cx);
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
Switch::new("proxy_user_avatars")
|
||||
Switch::new("proxy_avatar")
|
||||
.label(t!("preferences.proxy_avatars_label"))
|
||||
.description(t!("preferences.proxy_description"))
|
||||
.checked(proxy_avatar)
|
||||
.checked(proxy)
|
||||
.on_click(move |_, _window, cx| {
|
||||
AppSettings::update_proxy_user_avatars(!proxy_avatar, cx);
|
||||
AppSettings::update_proxy_user_avatars(!proxy, cx);
|
||||
}),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -5,7 +5,7 @@ pub const APP_UPDATER_ENDPOINT: &str = "https://coop-updater.reya.su/";
|
||||
pub const KEYRING_URL: &str = "Coop Safe Storage";
|
||||
|
||||
pub const ACCOUNT_IDENTIFIER: &str = "coop:user";
|
||||
pub const SETTINGS_D: &str = "coop:settings";
|
||||
pub const SETTINGS_IDENTIFIER: &str = "coop:settings";
|
||||
|
||||
/// Bootstrap Relays.
|
||||
pub const BOOTSTRAP_RELAYS: [&str; 5] = [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use anyhow::anyhow;
|
||||
use global::constants::SETTINGS_D;
|
||||
use global::constants::SETTINGS_IDENTIFIER;
|
||||
use global::nostr_client;
|
||||
use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task};
|
||||
use nostr_sdk::prelude::*;
|
||||
@@ -11,7 +11,7 @@ pub fn init(cx: &mut App) {
|
||||
|
||||
// Observe for state changes and save settings to database
|
||||
state.update(cx, |this, cx| {
|
||||
this.subscriptions
|
||||
this._subscriptions
|
||||
.push(cx.observe(&state, |this, _state, cx| {
|
||||
this.set_settings(cx);
|
||||
}));
|
||||
@@ -49,6 +49,7 @@ setting_accessors! {
|
||||
pub screening: bool,
|
||||
pub contact_bypass: bool,
|
||||
pub auto_login: bool,
|
||||
pub auto_auth: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@@ -60,6 +61,8 @@ pub struct Settings {
|
||||
pub screening: bool,
|
||||
pub contact_bypass: bool,
|
||||
pub auto_login: bool,
|
||||
pub auto_auth: bool,
|
||||
pub authenticated_relays: Vec<RelayUrl>,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
@@ -72,6 +75,8 @@ impl Default for Settings {
|
||||
screening: true,
|
||||
contact_bypass: true,
|
||||
auto_login: false,
|
||||
auto_auth: true,
|
||||
authenticated_relays: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,8 +93,8 @@ impl Global for GlobalAppSettings {}
|
||||
|
||||
pub struct AppSettings {
|
||||
setting_values: Settings,
|
||||
#[allow(dead_code)]
|
||||
subscriptions: SmallVec<[Subscription; 1]>,
|
||||
_subscriptions: SmallVec<[Subscription; 1]>,
|
||||
_tasks: SmallVec<[Task<()>; 1]>,
|
||||
}
|
||||
|
||||
impl AppSettings {
|
||||
@@ -110,54 +115,53 @@ impl AppSettings {
|
||||
|
||||
fn new(cx: &mut Context<Self>) -> Self {
|
||||
let setting_values = Settings::default();
|
||||
let mut subscriptions = smallvec![];
|
||||
let mut tasks = smallvec![];
|
||||
|
||||
subscriptions.push(cx.observe_new::<Self>(move |this, _window, cx| {
|
||||
this.get_settings_from_db(cx);
|
||||
}));
|
||||
|
||||
Self {
|
||||
setting_values,
|
||||
subscriptions,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_settings_from_db(&self, cx: &mut Context<Self>) {
|
||||
let task: Task<Result<Settings, anyhow::Error>> = cx.background_spawn(async move {
|
||||
let client = nostr_client();
|
||||
let filter = Filter::new()
|
||||
.kind(Kind::ApplicationSpecificData)
|
||||
.identifier(SETTINGS_D)
|
||||
.identifier(SETTINGS_IDENTIFIER)
|
||||
.limit(1);
|
||||
|
||||
if let Some(event) = nostr_client().database().query(filter).await?.first_owned() {
|
||||
log::info!("Successfully loaded settings from database");
|
||||
if let Some(event) = client.database().query(filter).await?.first_owned() {
|
||||
Ok(serde_json::from_str(&event.content).unwrap_or(Settings::default()))
|
||||
} else {
|
||||
Err(anyhow!("Not found"))
|
||||
}
|
||||
});
|
||||
|
||||
cx.spawn(async move |this, cx| {
|
||||
if let Ok(settings) = task.await {
|
||||
this.update(cx, |this, cx| {
|
||||
this.setting_values = settings;
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
tasks.push(
|
||||
// Load settings from database
|
||||
cx.spawn(async move |this, cx| {
|
||||
if let Ok(settings) = task.await {
|
||||
this.update(cx, |this, cx| {
|
||||
this.setting_values = settings;
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
Self {
|
||||
setting_values,
|
||||
_subscriptions: smallvec![],
|
||||
_tasks: tasks,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_settings(&self, cx: &mut Context<Self>) {
|
||||
if let Ok(content) = serde_json::to_string(&self.setting_values) {
|
||||
cx.background_spawn(async move {
|
||||
if let Ok(event) = EventBuilder::new(Kind::ApplicationSpecificData, content)
|
||||
.tags(vec![Tag::identifier(SETTINGS_D)])
|
||||
let client = nostr_client();
|
||||
let builder = EventBuilder::new(Kind::ApplicationSpecificData, content)
|
||||
.tags(vec![Tag::identifier(SETTINGS_IDENTIFIER)])
|
||||
.sign(&Keys::generate())
|
||||
.await
|
||||
{
|
||||
if let Err(e) = nostr_client().database().save_event(&event).await {
|
||||
.await;
|
||||
|
||||
if let Ok(event) = builder {
|
||||
if let Err(e) = client.database().save_event(&event).await {
|
||||
log::error!("Failed to save user settings: {e}");
|
||||
} else {
|
||||
log::info!("New settings have been saved successfully");
|
||||
@@ -167,4 +171,17 @@ impl AppSettings {
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_auto_auth(&self) -> bool {
|
||||
!self.setting_values.authenticated_relays.is_empty() && self.setting_values.auto_auth
|
||||
}
|
||||
|
||||
pub fn auth_relays(&self) -> Vec<RelayUrl> {
|
||||
self.setting_values.authenticated_relays.clone()
|
||||
}
|
||||
|
||||
pub fn push_auth_relay(&mut self, relay_url: RelayUrl, cx: &mut Context<Self>) {
|
||||
self.setting_values.authenticated_relays.push(relay_url);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user