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;
}
}
}

View File

@@ -10,6 +10,7 @@ impl From<ClientMsg> for ClientMsgInst {
ClientMsg::RequestMsgs => Self::RequestMsgsV0,
ClientMsg::SendMsg(v) => Self::SendMsgV0(v),
ClientMsg::Login(v) => Self::LoginV0(v),
ClientMsg::RequestUsers(v) => Self::RequestUsersV0(v),
}
}
}
@@ -21,6 +22,7 @@ impl From<ClientMsgInst> for ClientMsg {
ClientMsgInst::RequestMsgsV0 => Self::RequestMsgs,
ClientMsgInst::SendMsgV0(v) => Self::SendMsg(v),
ClientMsgInst::LoginV0(v) => Self::Login(v),
ClientMsgInst::RequestUsersV0(v) => Self::RequestUsers(v),
}
}
}
@@ -33,6 +35,7 @@ impl From<ServerMsg> for ServerMsgInst {
ServerMsg::LoadMsgs(v) => Self::LoadMsgsV0(v),
ServerMsg::ServerError(v) => Self::ServerErrorV0(v),
ServerMsg::LoginResp(v) => Self::LoginRespV0(v),
ServerMsg::RequestUsersResp(v) => Self::RequestUsersRespV0(v),
}
}
}
@@ -45,6 +48,7 @@ impl From<ServerMsgInst> for ServerMsg {
ServerMsgInst::LoadMsgsV0(v) => Self::LoadMsgs(v),
ServerMsgInst::ServerErrorV0(v) => Self::ServerError(v),
ServerMsgInst::LoginRespV0(v) => Self::LoginResp(v),
ServerMsgInst::RequestUsersRespV0(v) => Self::RequestUsersResp(v),
}
}
}

View File

@@ -7,6 +7,7 @@ pub enum ClientMsgInst {
LoginV0(LoginV0) = 1,
RequestMsgsV0 = 2,
SendMsgV0(SendMsgV0) = 3,
RequestUsersV0(RequestUsersV0) = 4,
}
#[repr(u32)]
@@ -17,6 +18,7 @@ pub enum ServerMsgInst {
LoadMsgV0(LoadMsgV0) = 2,
LoadMsgsV0(Vec<LoadMsgV0>) = 3,
ServerErrorV0(ServerErrorV0) = 4,
RequestUsersRespV0(RequestUsersRespV0) = 5,
}
pub type UserIdV0 = u64;
@@ -48,6 +50,20 @@ pub enum LoginRespV0 {
InvalidPassword,
}
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
pub struct RequestUsersV0;
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
pub struct RequestUsersRespV0 {
pub users: Vec<ServerUserV0>,
}
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
pub struct ServerUserV0 {
pub id: UserIdV0,
pub username: String,
}
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
pub struct LoginKeyV0(Vec<u8>);
impl LoginKeyV0 {
@@ -86,4 +102,5 @@ pub struct LoadMsgV0 {
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
pub enum ServerErrorV0 {
NotLoggedIn,
NoPermission,
}

View File

@@ -1,5 +1,5 @@
mod conversion;
mod data;
pub mod data;
mod msg;
mod no_cert;
mod request;

View File

@@ -1,4 +1,4 @@
use super::data::*;
use super::data;
#[derive(Debug)]
pub enum ClientMsg {
@@ -6,6 +6,7 @@ pub enum ClientMsg {
Login(Login),
RequestMsgs,
SendMsg(SendMsg),
RequestUsers(RequestUsers),
}
#[derive(Debug)]
@@ -15,17 +16,23 @@ pub enum ServerMsg {
LoadMsg(LoadMsg),
LoadMsgs(Vec<LoadMsg>),
ServerError(ServerError),
RequestUsersResp(RequestUsersResp),
}
pub type LoginKey = LoginKeyV0;
pub type SendMsg = SendMsgV0;
pub type LoadMsg = LoadMsgV0;
pub type ServerError = ServerErrorV0;
pub type CreateAccount = CreateAccountV0;
pub type CreateAccountResp = CreateAccountRespV0;
pub type Login = LoginV0;
pub type LoginResp = LoginRespV0;
pub type UserId = UserIdV0;
// TODO: a ton of this should really just be macros :sob:
pub use data::CreateAccountRespV0 as CreateAccountResp;
pub use data::CreateAccountV0 as CreateAccount;
pub use data::LoadMsgV0 as LoadMsg;
pub use data::LoginKeyV0 as LoginKey;
pub use data::LoginRespV0 as LoginResp;
pub use data::LoginV0 as Login;
pub use data::RequestUsersRespV0 as RequestUsersResp;
pub use data::RequestUsersV0 as RequestUsers;
pub use data::SendMsgV0 as SendMsg;
pub use data::ServerErrorV0 as ServerError;
pub use data::ServerUserV0 as ServerUser;
pub use data::UserIdV0 as UserId;
impl From<CreateAccount> for ClientMsg {
fn from(value: CreateAccount) -> Self {
@@ -39,6 +46,12 @@ impl From<Login> for ClientMsg {
}
}
impl From<RequestUsers> for ClientMsg {
fn from(value: RequestUsers) -> Self {
Self::RequestUsers(value)
}
}
impl From<ServerError> for ServerMsg {
fn from(value: ServerError) -> Self {
Self::ServerError(value)
@@ -62,3 +75,9 @@ impl From<LoginResp> for ServerMsg {
Self::LoginResp(value)
}
}
impl From<RequestUsersResp> for ServerMsg {
fn from(value: RequestUsersResp) -> Self {
Self::RequestUsersResp(value)
}
}

View File

@@ -1,7 +1,6 @@
use super::*;
use std::num::NonZeroU32;
use crate::net::{ClientMsgInst, ServerMsgInst};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, bitcode::Encode, bitcode::Decode)]
pub struct RequestId(NonZeroU32);
@@ -30,3 +29,44 @@ pub struct ServerRespMsg {
pub id: Option<RequestId>,
pub msg: ServerMsgInst,
}
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
}
}
}
impl RequestMsg for RequestUsers {
type Result = RequestUsersResp;
fn result(msg: ServerMsg) -> Option<Self::Result> {
if let ServerMsg::RequestUsersResp(res) = msg {
Some(res)
} else {
None
}
}
}