This commit is contained in:
2026-02-18 16:47:35 -05:00
parent f9d8fccf40
commit 97fdbbf968
16 changed files with 199 additions and 156 deletions

View File

@@ -1,22 +0,0 @@
use ed25519_dalek::SigningKey;
use rand::{
SeedableRng,
rngs::{StdRng, SysRng},
};
pub struct Account {
device_key: SigningKey,
account_key: SigningKey,
}
impl Account {
pub fn new() -> Account {
let mut csprng = StdRng::try_from_rng(&mut SysRng).unwrap();
let device_key = SigningKey::generate(&mut csprng);
let account_key = SigningKey::generate(&mut csprng);
Account {
device_key,
account_key,
}
}
}

View File

@@ -1,7 +1,7 @@
#![feature(async_fn_traits)]
#![windows_subsystem = "windows"]
use crate::{data::ClientData, state::ClientState};
use crate::data::ClientData;
use iris::prelude::*;
use openworm::net::{ServerMsg, install_crypto_provider};
use winit::{
@@ -9,11 +9,10 @@ use winit::{
window::WindowAttributes,
};
mod account;
mod data;
mod debug;
mod net;
mod state;
mod session;
mod ui;
fn main() {
@@ -25,10 +24,8 @@ fn main() {
pub struct Client {
ui_state: DefaultUiState,
data: ClientData,
state: ClientState,
main_ui: WeakWidget<WidgetPtr>,
notif: WeakWidget<WidgetPtr>,
proxy: Proxy<ClientEvent>,
}
pub type Rsc = DefaultRsc<Client>;
@@ -44,7 +41,7 @@ impl DefaultAppState for Client {
fn new(
mut ui_state: DefaultUiState,
rsc: &mut DefaultRsc<Self>,
proxy: Proxy<Self::Event>,
_: Proxy<Self::Event>,
) -> Self {
let notif = WidgetPtr::default().add(rsc);
let main_ui = WidgetPtr::default().add(rsc);
@@ -68,10 +65,8 @@ impl DefaultAppState for Client {
Self {
ui_state,
data,
state: Default::default(),
main_ui,
notif,
proxy,
}
}
@@ -89,10 +84,6 @@ impl DefaultAppState for Client {
}
}
fn exit(&mut self, _rsc: &mut DefaultRsc<Self>, _render: &mut UiRenderState) {
self.state.exit();
}
fn window_event(
&mut self,
event: WindowEvent,

View File

@@ -1,8 +1,8 @@
use crate::ClientEvent;
use dashmap::DashMap;
use openworm::net::{
ClientMsg, ClientRequestMsg, CreateAccount, CreateAccountResp, Login, LoginResp, RecvHandler,
RequestId, SERVER_NAME, ServerMsg, ServerRespMsg, SkipServerVerification, recv_uni, send_uni,
ClientMsg, ClientRequestMsg, RecvHandler, RequestId, RequestMsg, SERVER_NAME, ServerMsg,
ServerRespMsg, SkipServerVerification, recv_uni, send_uni,
};
use quinn::{
ClientConfig, Connection, Endpoint, IdleTimeout, TransportConfig,
@@ -25,6 +25,7 @@ pub struct ConnectInfo {
pub cert: Vec<u8>,
}
#[derive(Clone)]
pub struct NetHandle {
send: UnboundedSender<NetCtrlMsg>,
}
@@ -75,35 +76,6 @@ impl NetHandle {
}
}
pub trait RequestMsg: Into<ClientMsg> {
type Result;
fn result(msg: ServerMsg) -> Option<Self::Result>;
}
impl RequestMsg for CreateAccount {
type Result = CreateAccountResp;
fn result(msg: ServerMsg) -> Option<Self::Result> {
if let ServerMsg::CreateAccountResp(res) = msg {
Some(res)
} else {
None
}
}
}
impl RequestMsg for Login {
type Result = LoginResp;
fn result(msg: ServerMsg) -> Option<Self::Result> {
if let ServerMsg::LoginResp(res) = msg {
Some(res)
} else {
None
}
}
}
async fn connection_cert(
addr: SocketAddr,
cert: CertificateDer<'_>,
@@ -118,8 +90,7 @@ async fn connection_cert(
let client_config = ClientConfig::new(Arc::new(
QuicClientConfig::try_from(client_crypto).map_err(|e| e.to_string())?,
));
let mut endpoint = quinn::Endpoint::client(SocketAddr::from_str("[::]:0").unwrap())
.map_err(|e| e.to_string())?;
let mut endpoint = quinn::Endpoint::client(CLIENT_SOCKET).map_err(|e| e.to_string())?;
endpoint.set_default_client_config(client_config);
let conn = endpoint
.connect(addr, SERVER_NAME)

View File

@@ -0,0 +1,8 @@
use openworm::net::UserId;
use crate::net::NetHandle;
pub struct Session {
pub con: NetHandle,
pub user_id: UserId,
}

View File

@@ -1,50 +0,0 @@
use crate::net::NetHandle;
use iris::prelude::*;
use openworm::net::LoadMsg;
use std::thread::JoinHandle;
#[derive(Default)]
pub struct Connect {
pub handle: Option<JoinHandle<()>>,
}
pub struct Login {
pub handle: NetHandle,
}
pub struct LoggedIn {
pub network: NetHandle,
pub msgs: Vec<LoadMsg>,
pub channel: Option<WeakWidget<Span>>,
pub username: String,
}
pub enum ClientState {
Connect(Connect),
Login(Login),
LoggedIn(LoggedIn),
}
impl Default for ClientState {
fn default() -> Self {
Self::Connect(Default::default())
}
}
impl ClientState {
pub fn take(&mut self) -> Self {
std::mem::take(self)
}
pub fn exit(&mut self) {
let s = self.take();
match s {
ClientState::Connect(_) => (),
ClientState::Login(Login { handle }) => {
handle.exit();
}
ClientState::LoggedIn(state) => {
state.network.exit();
}
}
}
}

View File

@@ -1,6 +1,7 @@
use openworm::net::{CreateAccount, CreateAccountResp, Login, LoginResp};
use crate::{
session::Session,
data::{AccountInfo, ClientData, ServerInfo, ServerList},
net::{ConnectInfo, NetHandle},
};
@@ -53,7 +54,7 @@ pub fn start(rsc: &mut Rsc, data: &ClientData) -> WeakWidget {
rsc[ctx.notif].inner = Some(werror(&reason, rsc));
})
};
let net = match NetHandle::connect(
let con = match NetHandle::connect(
async |msg| {
println!("msg recv :joy:");
},
@@ -70,7 +71,7 @@ pub fn start(rsc: &mut Rsc, data: &ClientData) -> WeakWidget {
}
};
let Ok(resp) = net
let Ok(resp) = con
.request(Login {
username: account.username.clone(),
password: password.clone(),
@@ -79,7 +80,7 @@ pub fn start(rsc: &mut Rsc, data: &ClientData) -> WeakWidget {
else {
return fail("failed to create account");
};
let id = match resp {
let user_id = match resp {
LoginResp::Ok { id } => id,
LoginResp::UnknownUsername => {
return fail("unknown username");
@@ -88,8 +89,9 @@ pub fn start(rsc: &mut Rsc, data: &ClientData) -> WeakWidget {
return fail("invalid password");
}
};
let session = Session { con, user_id };
ctx.update(move |ctx, rsc| {
main_view(rsc).set_ptr(ctx.main_ui, rsc);
main_view(rsc, session).set_ptr(ctx.main_ui, rsc);
});
});
});
@@ -148,7 +150,7 @@ pub fn create_account(rsc: &mut Rsc) -> WeakWidget {
})
};
keyring::use_native_store(true).unwrap();
let net = match NetHandle::connect(
let con = match NetHandle::connect(
async |msg| {
println!("msg recv :joy:");
},
@@ -165,7 +167,7 @@ pub fn create_account(rsc: &mut Rsc) -> WeakWidget {
}
};
let Ok(resp) = net
let Ok(resp) = con
.request(CreateAccount {
username: username.clone(),
password: password.clone(),
@@ -175,7 +177,7 @@ pub fn create_account(rsc: &mut Rsc) -> WeakWidget {
else {
return fail("failed to create account");
};
let id = match resp {
let user_id = match resp {
CreateAccountResp::Ok { id } => id,
CreateAccountResp::UsernameExists => {
return fail("username already exists");
@@ -184,8 +186,9 @@ pub fn create_account(rsc: &mut Rsc) -> WeakWidget {
return fail("invalid account token");
}
};
let session = Session { con, user_id };
ctx.update(move |ctx, rsc| {
main_view(rsc).set_ptr(ctx.main_ui, rsc);
main_view(rsc, session).set_ptr(ctx.main_ui, rsc);
ctx.data.create_account(
ServerInfo { cert_hex },
AccountInfo { url, username },

View File

@@ -1,6 +1,6 @@
use std::hash::Hash;
use crate::Rsc;
use crate::{Rsc, session::Session};
use super::*;
@@ -9,24 +9,25 @@ pub const SIZE: u32 = 20;
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
pub enum MainView {
Channel,
Friends,
Server,
}
pub fn main_view(rsc: &mut Rsc) -> WeakWidget {
pub fn main_view(rsc: &mut Rsc, session: Session) -> WeakWidget {
let mut view = WidgetSelector::new(MainView::Channel, channel::view(rsc));
view.set(MainView::Server, server::view(rsc));
view.set(MainView::Server, server::view(rsc, &session));
let view = view.add(rsc);
(top_bar(rsc, view), view).span(Dir::DOWN).add(rsc)
}
pub fn top_bar(rsc: &mut Rsc, view: WeakWidget<WidgetSelector<MainView>>) -> WeakWidget {
let [channel, server] = tabs(
let [channel, friends, server] = tabs(
rsc,
view,
[("channel", MainView::Channel), ("server", MainView::Server)],
[
("channel", MainView::Channel),
("friends", MainView::Friends),
("server", MainView::Server),
],
);
rect(Color::BLACK.alpha(150))
let top_bar = rect(Color::BLACK.alpha(150))
.height(50)
.foreground((channel, server).span(Dir::RIGHT))
.add(rsc)
.foreground((channel, friends, server).span(Dir::RIGHT));
(top_bar, view).span(Dir::DOWN).add(rsc)
}

View File

@@ -12,6 +12,10 @@ pub fn hint_text(msg: impl Into<String>) -> TextBuilder<Rsc> {
wtext(msg).size(20).color(Color::GRAY)
}
pub fn large_hint_text(msg: impl Into<String>) -> TextBuilder<Rsc> {
wtext(msg).size(30).color(Color::GRAY)
}
pub fn field(default: &str, hint: &str, rsc: &mut Rsc) -> WeakWidget<TextEdit> {
wtext(default)
.editable(EditMode::SingleLine)

View File

@@ -1,3 +1,7 @@
use openworm::net::RequestUsers;
use crate::session::Session;
use super::*;
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
@@ -6,9 +10,9 @@ enum View {
User,
}
pub fn view(rsc: &mut Rsc) -> StrongWidget {
pub fn view(rsc: &mut Rsc, session: &Session) -> StrongWidget {
let mut view = WidgetSelector::new(View::Info, info(rsc));
view.set(View::User, users(rsc));
view.set(View::User, users(rsc, session));
let view = view.add(rsc);
let [info, server] = tabs(rsc, view, [("info", View::Info), ("users", View::User)]);
@@ -23,6 +27,36 @@ fn info(rsc: &mut Rsc) -> StrongWidget {
wtext("server info").center_text().add_strong(rsc)
}
fn users(rsc: &mut Rsc) -> StrongWidget {
wtext("users").center_text().add_strong(rsc)
fn users(rsc: &mut Rsc, session: &Session) -> StrongWidget {
let ptr = WidgetPtr::new(
large_hint_text("loading users...")
.center_text()
.width(rest(1))
.add_strong(rsc),
)
.add(rsc);
let con = session.con.clone();
rsc.events.register(ptr, Draw, move |_, rsc| {
let con = con.clone();
rsc.spawn_task(async move |mut ctx| {
let Ok(resp) = con.request(RequestUsers).await else {
return;
};
ctx.update(move |_, rsc| {
let mut span = Span::empty(Dir::DOWN);
for user in resp.users {
let thing = (
wtext(user.id.to_string()).size(20),
wtext(user.username).size(20),
)
.span(Dir::RIGHT)
.gap(30)
.pad(15);
span.push(thing.add_strong(rsc));
}
span.set_ptr(ptr, rsc);
});
});
});
ptr.upgrade(rsc)
}

View File

@@ -8,6 +8,7 @@ pub struct ServerPermsV0(u32);
impl ServerPermsV0 {
pub const NONE: Self = Self(0);
pub const ACCOUNT_TOKENS: Self = Self(1 << 0);
pub const ALL: Self = Self(u32::MAX);
}
#[derive(bitcode::Encode, bitcode::Decode)]

View File

@@ -7,7 +7,8 @@ use net::{ClientSender, ConAccepter, listen};
use openworm::{
net::{
ClientMsg, ClientRequestMsg, CreateAccount, CreateAccountResp, DisconnectReason, LoadMsg,
Login, LoginResp, RecvHandler, ServerError, ServerMsg, install_crypto_provider,
Login, LoginResp, RecvHandler, RequestUsersResp, ServerError, ServerMsg, ServerUser,
install_crypto_provider,
},
rsc::DataDir,
};
@@ -49,7 +50,7 @@ pub async fn run_server(port: u16) {
db: db.clone(),
};
if db.users.is_empty() {
let token = account_token(&db, ServerPerms::ACCOUNT_TOKENS);
let token = account_token(&db, ServerPerms::ALL);
println!("no users found, token for admin: {token}");
}
let (endpoint, handle) = listen(port, &dir.path, handler);
@@ -231,6 +232,27 @@ impl RecvHandler<ClientRequestMsg> for ClientHandler {
*self.state.write().await = ClientState::Authed(id);
let _ = replier.send(LoginResp::Ok { id }).await;
}
ClientMsg::RequestUsers(_) => {
if self
.db
.users
.get(&self.id)
.is_some_and(|u| !u.server_perms.contains(ServerPerms::ALL))
{
let _ = replier.send(ServerError::NoPermission).await;
return;
}
let users: Vec<_> = self
.db
.users
.iter()
.map(|(id, u)| ServerUser {
id,
username: u.username,
})
.collect();
let _ = replier.send(RequestUsersResp { users }).await;
}
}
}