server side preparation

This commit is contained in:
2026-02-18 21:52:22 -05:00
parent 1c6b6de8f6
commit 6fad5b1852
15 changed files with 547 additions and 535 deletions

View File

@@ -1,8 +1,8 @@
use crate::ClientEvent; use crate::ClientEvent;
use dashmap::DashMap; use dashmap::DashMap;
use openworm::net::{ use openworm::net::{
ClientMsg, ClientRequestMsg, RecvHandler, RequestId, RequestMsg, SERVER_NAME, ServerMsg, ClientMsg, ClientMsgInst, RecvHandler, RequestId, RequestMsg, SERVER_NAME, ServerMsg,
ServerRespMsg, SkipServerVerification, recv_uni, send_uni, ServerMsgInst, SkipServerVerification, recv_uni, send_uni,
}; };
use quinn::{ use quinn::{
ClientConfig, Connection, Endpoint, IdleTimeout, TransportConfig, ClientConfig, Connection, Endpoint, IdleTimeout, TransportConfig,
@@ -10,12 +10,10 @@ use quinn::{
}; };
use std::{ use std::{
net::{Ipv6Addr, SocketAddr, SocketAddrV6, ToSocketAddrs}, net::{Ipv6Addr, SocketAddr, SocketAddrV6, ToSocketAddrs},
str::FromStr,
sync::Arc, sync::Arc,
time::Duration, time::Duration,
}; };
use tokio::sync::{mpsc::UnboundedSender, oneshot}; use tokio::sync::{mpsc::UnboundedSender, oneshot};
use winit::event_loop::EventLoopProxy;
pub const CLIENT_SOCKET: SocketAddr = pub const CLIENT_SOCKET: SocketAddr =
SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, 0, 0, 0)); SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, 0, 0, 0));
@@ -30,21 +28,8 @@ pub struct NetHandle {
send: UnboundedSender<NetCtrlMsg>, send: UnboundedSender<NetCtrlMsg>,
} }
#[derive(Clone)]
pub struct AppHandle {
pub proxy: EventLoopProxy<ClientEvent>,
}
impl AppHandle {
pub fn send(&self, event: ClientEvent) {
self.proxy.send_event(event).unwrap_or_else(|_| panic!());
}
}
type NetResult<T> = Result<T, String>; type NetResult<T> = Result<T, String>;
pub trait ClientRequest {}
pub enum NetCtrlMsg { pub enum NetCtrlMsg {
Send(ClientMsg), Send(ClientMsg),
Request(ClientMsg, oneshot::Sender<ServerMsg>), Request(ClientMsg, oneshot::Sender<ServerMsg>),
@@ -156,9 +141,9 @@ impl NetHandle {
let request_id = req_id.next(); let request_id = req_id.next();
match msg { match msg {
NetCtrlMsg::Send(msg) => { NetCtrlMsg::Send(msg) => {
let msg = ClientRequestMsg { let msg = ClientMsgInst {
id: request_id, id: request_id,
msg: msg.into(), msg,
}; };
if send_uni(&conn, msg).await.is_err() { if send_uni(&conn, msg).await.is_err() {
println!("disconnected from server"); println!("disconnected from server");
@@ -166,9 +151,9 @@ impl NetHandle {
} }
} }
NetCtrlMsg::Request(msg, send) => { NetCtrlMsg::Request(msg, send) => {
let msg = ClientRequestMsg { let msg = ClientMsgInst {
id: request_id, id: request_id,
msg: msg.into(), msg,
}; };
recv.requests.insert(request_id, send); recv.requests.insert(request_id, send);
if send_uni(&conn, msg).await.is_err() { if send_uni(&conn, msg).await.is_err() {
@@ -206,15 +191,14 @@ struct ServerRecv<F: MsgHandler> {
msg: F, msg: F,
} }
impl<F: MsgHandler> RecvHandler<ServerRespMsg> for ServerRecv<F> { impl<F: MsgHandler> RecvHandler<ServerMsgInst> for ServerRecv<F> {
async fn msg(&self, resp: ServerRespMsg) { async fn msg(&self, resp: ServerMsgInst) {
let msg = resp.msg.into();
if let Some(id) = resp.id if let Some(id) = resp.id
&& let Some((_, send)) = self.requests.remove(&id) && let Some((_, send)) = self.requests.remove(&id)
{ {
send.send(msg); let _ = send.send(resp.msg);
} else { } else {
self.msg.run(msg).await; self.msg.run(resp.msg).await;
} }
} }
} }

View File

@@ -0,0 +1,12 @@
// pub fn view(rsc: &mut Rsc, session: &Session) -> StrongWidget {
// let mut view = WidgetSelector::new(View::Info, info(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)]);
//
// let side_bar = rect(Color::BLACK.alpha(150))
// .foreground((info, server).span(Dir::DOWN))
// .width(260);
//
// (side_bar, view).span(Dir::RIGHT).add_strong(rsc)
// }

View File

@@ -4,6 +4,7 @@ use iris::prelude::*;
mod channel; mod channel;
pub mod color; pub mod color;
mod connect; mod connect;
mod friends;
mod main; mod main;
mod misc; mod misc;
mod server; mod server;

48
src/bin/server/db/data.rs Normal file
View File

@@ -0,0 +1,48 @@
use iris::core::util::HashSet;
pub use openworm::net::{AccountToken, ServerPerms};
pub type UserId = u64;
pub type MsgId = i128;
pub type ChannelId = u64;
pub type ImageId = u64;
#[derive(bitcode::Encode, bitcode::Decode)]
pub struct User {
pub username: String,
pub password_hash: String,
pub pfp: Option<ImageId>,
pub bio: String,
pub friends: Friends,
pub server_perms: ServerPerms,
}
impl User {
pub fn new(username: String, password_hash: String, perms: ServerPerms) -> Self {
Self {
username,
password_hash,
bio: String::new(),
pfp: None,
friends: Default::default(),
server_perms: perms,
}
}
}
#[derive(Default, bitcode::Encode, bitcode::Decode)]
pub struct Friends {
pub current: HashSet<UserId>,
pub outgoing: HashSet<UserId>,
pub incoming: HashSet<UserId>,
}
#[derive(bitcode::Encode, bitcode::Decode)]
pub struct Msg {
pub content: String,
pub author: UserId,
}
pub struct Channel {
pub name: String,
pub desc: String,
}

View File

@@ -1,31 +1,22 @@
mod data;
mod util; mod util;
mod ver;
use std::path::Path; use std::path::Path;
pub use data::*;
use util::*; use util::*;
use ver::*;
pub const DB_VERSION: u64 = 0; pub const DB_VERSION: u64 = 0;
#[derive(Clone)] #[derive(Clone)]
pub struct Db { pub struct Db {
db: Database, db: Database,
pub account_tokens: DbMap<String, ServerPerms>, pub account_tokens: DbMap<AccountToken, ServerPerms>,
pub msgs: DbMap<MsgId, Msg>, pub msgs: DbMap<MsgId, Msg>,
pub users: DbMap<UserId, User>, pub users: DbMap<UserId, User>,
pub usernames: DbMap<String, UserId>, pub usernames: DbMap<String, UserId>,
} }
pub type UserId = UserIdV0;
pub type MsgId = MsgIdV0;
pub type ChannelId = ChannelIdV0;
pub type ImageId = ImageIdV0;
pub type User = UserV0;
pub type Msg = MsgV0;
pub type ChannelInfo = ChannelV0;
pub type ServerPerms = ServerPermsV0;
impl Db { impl Db {
pub fn open(path: impl AsRef<Path>) -> Db { pub fn open(path: impl AsRef<Path>) -> Db {
let db = Database::open(path); let db = Database::open(path);

View File

@@ -17,7 +17,7 @@ impl<K, V> Clone for DbMap<K, V> {
Self { Self {
db: self.db.clone(), db: self.db.clone(),
keyspace: self.keyspace.clone(), keyspace: self.keyspace.clone(),
_pd: self._pd.clone(), _pd: self._pd,
} }
} }
} }
@@ -126,7 +126,7 @@ impl WriteTx {
}) })
} }
pub fn get<K: Key<Output = K>, V: DecodeOwned>(&self, map: &DbMap<K, V>, k: K) -> Option<V> { pub fn get<K: Key<Output = K>, V: DecodeOwned>(&self, map: &DbMap<K, V>, k: &K) -> Option<V> {
let k = Slice::new(k.to_bytes().as_ref()); let k = Slice::new(k.to_bytes().as_ref());
let v = self.0.get(&map.keyspace, k).unwrap()?; let v = self.0.get(&map.keyspace, k).unwrap()?;
Some(bitcode::decode(&v).unwrap()) Some(bitcode::decode(&v).unwrap())
@@ -137,7 +137,11 @@ impl WriteTx {
self.0.get(&map.keyspace, k).unwrap().is_some() self.0.get(&map.keyspace, k).unwrap().is_some()
} }
pub fn remove<K: Key<Output = K>, V: DecodeOwned>(&mut self, map: &DbMap<K, V>, k: K) -> Option<V> { pub fn remove<K: Key<Output = K>, V: DecodeOwned>(
&mut self,
map: &DbMap<K, V>,
k: K,
) -> Option<V> {
let k = Slice::new(k.to_bytes().as_ref()); let k = Slice::new(k.to_bytes().as_ref());
let v = self.0.take(&map.keyspace, k).unwrap()?; let v = self.0.take(&map.keyspace, k).unwrap()?;
Some(bitcode::decode(&v).unwrap()) Some(bitcode::decode(&v).unwrap())

View File

@@ -1,38 +0,0 @@
pub type UserIdV0 = u64;
pub type MsgIdV0 = i128;
pub type ChannelIdV0 = u64;
pub type ImageIdV0 = u64;
#[derive(Clone, Copy, bitcode::Encode, bitcode::Decode)]
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)]
pub struct UserV0 {
pub username: String,
pub password_hash: String,
pub pfp: Option<ImageIdV0>,
pub bio: String,
pub server_perms: ServerPermsV0,
}
#[derive(bitcode::Encode, bitcode::Decode)]
pub struct MsgV0 {
pub content: String,
pub author: UserIdV0,
}
pub struct ChannelV0 {
pub name: String,
pub desc: String,
}
impl ServerPermsV0 {
pub fn contains(&self, other: Self) -> bool {
(self.0 & other.0) == other.0
}
}

313
src/bin/server/handle.rs Normal file
View File

@@ -0,0 +1,313 @@
use super::net::ClientSender;
use crate::{
ClientId, account_token,
db::{Db, ServerPerms, User, UserId},
net::ClientReplier,
};
use openworm::net::*;
use scrypt::{
Scrypt,
password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString, rand_core::OsRng},
};
use std::{collections::HashMap, sync::Arc};
use tokio::sync::RwLock;
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum ClientState {
Login,
Authed(UserId),
}
pub struct ClientHandler {
pub db: Db,
pub send: ClientSender,
pub senders: Arc<RwLock<HashMap<ClientId, ClientSender>>>,
pub id: ClientId,
pub state: Arc<RwLock<ClientState>>,
}
impl ClientHandler {
pub async fn handle_msg(&self, msg: ClientMsg, replier: &ClientReplier) -> Option<()> {
// TODO: there are some awful edge cases where you delete an account and it's in the middle
// of a non TX transaction; make sure to use TX, maybe need another check_login closure
// fortunately these should be very rare so I'ma ignore for now
macro_rules! reply {
($msg:expr) => {
replier.send($msg).await;
return None;
};
}
let check_login = async || {
if let ClientState::Authed(uid) = &*self.state.read().await {
Some(*uid)
} else {
reply!(NotLoggedIn);
}
};
let check_user = async || {
if let ClientState::Authed(uid) = &*self.state.read().await {
if let Some(user) = self.db.users.get(uid) {
Some((*uid, user))
} else {
reply!(AccountDeleted);
}
} else {
reply!(NotLoggedIn);
}
};
let check_server_perms = async |perms| {
let id = check_login().await?;
if self
.db
.users
.get(&id)
.is_some_and(|u| !u.server_perms.contains(perms))
{
reply!(NoPermission);
} else {
Some(())
}
};
let db = &self.db;
match msg {
ClientMsg::GenerateToken(info) => {
let check = if info.perms == ServerPerms::NONE {
ServerPerms::ACCOUNT_TOKENS
} else {
ServerPerms::ALL
};
check_server_perms(check).await?;
let token = account_token(&self.db, info.perms);
reply!(GenerateTokenResp { token });
}
ClientMsg::CreateAccount(info) => {
let CreateAccount {
token,
username,
password,
} = &info;
let salt = SaltString::generate(&mut OsRng);
let params = scrypt::Params::new(11, 8, 1, 32).unwrap();
let hash = Scrypt
.hash_password_customized(password.as_bytes(), None, None, params, &salt)
.unwrap()
.to_string();
let mut id;
loop {
let mut tx = db.write_tx();
let Some(perms) = tx.remove(&db.account_tokens, token.to_string()) else {
println!("invalid token: {:?}", self.send.remote());
reply!(CreateAccountResp::InvalidToken);
};
if tx.has_key(&db.usernames, username.clone()) {
reply!(CreateAccountResp::UsernameExists);
}
id = rand::random();
while tx.has_key(&db.users, id) {
id = rand::random();
}
tx.insert(
&db.users,
&id,
&User::new(username.clone(), hash.clone(), perms),
);
tx.insert(&db.usernames, username, &id);
if tx.commit() {
break;
}
}
println!("account created: \"{username}\"");
*self.state.write().await = ClientState::Authed(id);
reply!(CreateAccountResp::Ok { id });
}
ClientMsg::Login(info) => {
let Login { username, password } = &info;
let Some(id) = db.usernames.get(username) else {
reply!(LoginResp::UnknownUsername);
};
let Some(user) = db.users.get(&id) else {
panic!("invalid state! (should be a user)");
};
let hash = PasswordHash::new(&user.password_hash).unwrap();
if Scrypt.verify_password(password.as_bytes(), &hash).is_err() {
println!("invalid password: \"{username}\"");
reply!(LoginResp::InvalidPassword);
}
*self.state.write().await = ClientState::Authed(id);
reply!(LoginResp::Ok { id });
}
ClientMsg::RequestUsers(_) => {
check_login().await?;
check_server_perms(ServerPerms::ALL).await?;
let users: Vec<_> = self
.db
.users
.iter()
.map(|(id, u)| ServerUser {
id,
username: u.username,
})
.collect();
reply!(RequestUsersResp { users });
}
ClientMsg::AddFriend(info) => {
let user_id = check_login().await?;
loop {
let mut tx = db.write_tx();
let Some(mut user) = tx.get(&db.users, &user_id) else {
reply!(AccountDeleted);
};
let Some(friend_id) = tx.get(&db.usernames, &info.username) else {
reply!(AddFriendResp::UnknownUser);
};
if user.friends.outgoing.contains(&friend_id) {
reply!(AddFriendResp::AlreadySent);
}
if friend_id == user_id {
reply!(AddFriendResp::CannotAddSelf);
}
let Some(mut other) = tx.get(&db.users, &user_id) else {
println!("WARNING: username without valid user!");
reply!(AddFriendResp::UnknownUser);
};
if other.friends.current.contains(&user_id) {
reply!(AddFriendResp::AlreadyFriends);
}
user.friends.outgoing.insert(friend_id);
other.friends.incoming.insert(user_id);
tx.insert(&db.users, &user_id, &user);
tx.insert(&db.users, &friend_id, &other);
if tx.commit() {
break;
}
}
reply!(AddFriendResp::Ok);
}
ClientMsg::RemoveFriend(info) => {
let user_id = check_login().await?;
loop {
let mut tx = db.write_tx();
let mut user = tx.get(&db.users, &user_id)?;
let friend_id = info.id;
let Some(mut other) = tx.get(&db.users, &user_id) else {
println!("WARNING: username without valid user!");
return None;
};
user.friends.current.remove(&friend_id);
other.friends.current.remove(&user_id);
tx.insert(&db.users, &user_id, &user);
tx.insert(&db.users, &friend_id, &other);
if tx.commit() {
break;
}
}
reply!(AddFriendResp::Ok);
}
ClientMsg::RequestFriends(_) => {
let user = check_user().await?.1;
reply!(RequestFriendsResp {
current: user.friends.current,
incoming: user.friends.incoming,
outgoing: user.friends.outgoing,
});
}
ClientMsg::AnswerFriendRequest(answer) => {
let user_id = check_login().await?;
loop {
let mut tx = db.write_tx();
let mut user = tx.get(&db.users, &user_id)?;
let other_id = answer.id;
let Some(mut other) = tx.get(&db.users, &user_id) else {
println!("WARNING: username without valid user!");
return None;
};
match answer.action {
FriendRequestAction::Accept => {
user.friends.incoming.remove(&other_id);
other.friends.outgoing.remove(&user_id);
user.friends.current.insert(other_id);
other.friends.current.insert(user_id);
}
FriendRequestAction::Deny => {
user.friends.incoming.remove(&other_id);
other.friends.outgoing.remove(&user_id);
}
}
tx.insert(&db.users, &user_id, &user);
tx.insert(&db.users, &other_id, &other);
if tx.commit() {
break;
}
}
None
}
}
}
}
impl RecvHandler<ClientMsgInst> for ClientHandler {
async fn connect(&self) -> () {}
async fn msg(&self, req: ClientMsgInst) {
let replier = self.send.replier(req.id);
self.handle_msg(req.msg, &replier).await;
}
async fn disconnect(&self, reason: DisconnectReason) -> () {
match reason {
DisconnectReason::Closed | DisconnectReason::Timeout => (),
DisconnectReason::Other(e) => println!("connection issue: {e}"),
}
}
}
// ClientMsg::SendMsg(msg) => {
// let uid = check_login().await?;
// let msg = Msg {
// author: uid,
// content: msg.content,
// };
// // TODO: it is technically possible to send 2 messages at the exact same time...
// // should probably append a number if one already exists at that time,
// // but also I can't see this ever happening...?
// // should be an easy fix later (write tx)
// let timestamp = time::OffsetDateTime::now_utc().unix_timestamp_nanos();
// self.db.msgs.insert(&timestamp, &msg);
// let mut handles = Vec::new();
// let msg = LoadMsg {
// content: msg.content,
// author: uid,
// };
// for (&id, send) in self.senders.read().await.iter() {
// if id == self.id {
// continue;
// }
// let send = send.clone();
// let msg = msg.clone();
// let fut = async move {
// let _ = send
// .send(LoadMsg {
// content: msg.content,
// author: msg.author,
// })
// .await;
// };
// handles.push(tokio::spawn(fut));
// }
// for h in handles {
// h.await.unwrap();
// }
// None
// }
// ClientMsg::RequestMsgs => {
// check_login().await?;
// let msgs = self
// .db
// .msgs
// .values()
// .map(|msg| LoadMsg {
// content: msg.content,
// author: msg.author,
// })
// .collect();
// replier.send(ServerMsg::LoadMsgs(msgs)).await;
// }

View File

@@ -1,22 +1,18 @@
mod db; mod db;
mod handle;
mod net; mod net;
use crate::db::{Db, Msg, ServerPerms, User}; use crate::{
db::{Db, ServerPerms},
handle::{ClientHandler, ClientState},
};
use clap::Parser; use clap::Parser;
use net::{ClientSender, ConAccepter, listen}; use net::{ClientSender, ConAccepter, listen};
use openworm::{ use openworm::{
net::{ net::{AccountToken, ClientMsgInst, RecvHandler, install_crypto_provider},
ClientMsg, ClientRequestMsg, CreateAccount, CreateAccountResp, DisconnectReason, LoadMsg,
Login, LoginResp, RecvHandler, RequestUsersResp, ServerError, ServerMsg, ServerUser,
install_crypto_provider,
},
rsc::DataDir, rsc::DataDir,
}; };
use rand::distr::{Alphanumeric, SampleString}; use rand::distr::{Alphanumeric, SampleString};
use scrypt::{
Scrypt,
password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString, rand_core::OsRng},
};
use std::{ use std::{
collections::HashMap, collections::HashMap,
sync::{ sync::{
@@ -62,7 +58,7 @@ pub async fn run_server(port: u16) {
endpoint.wait_idle().await; endpoint.wait_idle().await;
} }
pub fn account_token(db: &Db, perms: ServerPerms) -> String { pub fn account_token(db: &Db, perms: ServerPerms) -> AccountToken {
let token = Alphanumeric.sample_string(&mut rand::rng(), 16); let token = Alphanumeric.sample_string(&mut rand::rng(), 16);
db.account_tokens.insert(&token, &perms); db.account_tokens.insert(&token, &perms);
token token
@@ -76,14 +72,8 @@ struct ServerListener {
count: AtomicU64, count: AtomicU64,
} }
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum ClientState {
Login,
Authed(u64),
}
impl ConAccepter for ServerListener { impl ConAccepter for ServerListener {
async fn accept(&self, send: ClientSender) -> impl RecvHandler<ClientRequestMsg> { async fn accept(&self, send: ClientSender) -> impl RecvHandler<ClientMsgInst> {
let id = self.count.fetch_add(1, Ordering::Release); let id = self.count.fetch_add(1, Ordering::Release);
self.senders.write().await.insert(id, send.clone()); self.senders.write().await.insert(id, send.clone());
ClientHandler { ClientHandler {
@@ -95,177 +85,3 @@ impl ConAccepter for ServerListener {
} }
} }
} }
struct ClientHandler {
db: Db,
send: ClientSender,
senders: Arc<RwLock<HashMap<ClientId, ClientSender>>>,
id: ClientId,
state: Arc<RwLock<ClientState>>,
}
impl RecvHandler<ClientRequestMsg> for ClientHandler {
async fn connect(&self) -> () {}
async fn msg(&self, req: ClientRequestMsg) {
let msg = ClientMsg::from(req.msg);
let replier = self.send.replier(req.id);
match msg {
ClientMsg::SendMsg(msg) => {
let ClientState::Authed(uid) = &*self.state.read().await else {
let _ = replier.send(ServerError::NotLoggedIn).await;
return;
};
let msg = Msg {
author: *uid,
content: msg.content,
};
// TODO: it is technically possible to send 2 messages at the exact same time...
// should probably append a number if one already exists at that time,
// but also I can't see this ever happening...?
// should be an easy fix later (write tx)
let timestamp = time::OffsetDateTime::now_utc().unix_timestamp_nanos();
self.db.msgs.insert(&timestamp, &msg);
let mut handles = Vec::new();
let msg = LoadMsg {
content: msg.content,
author: *uid,
};
for (&id, send) in self.senders.read().await.iter() {
if id == self.id {
continue;
}
let send = send.clone();
let msg = msg.clone();
let fut = async move {
let _ = send
.send(LoadMsg {
content: msg.content,
author: msg.author,
})
.await;
};
handles.push(tokio::spawn(fut));
}
for h in handles {
h.await.unwrap();
}
}
ClientMsg::RequestMsgs => {
let ClientState::Authed(_uid) = &*self.state.read().await else {
let _ = replier.send(ServerError::NotLoggedIn).await;
return;
};
let msgs = self
.db
.msgs
.values()
.map(|msg| LoadMsg {
content: msg.content,
author: msg.author,
})
.collect();
let _ = replier.send(ServerMsg::LoadMsgs(msgs)).await;
}
ClientMsg::CreateAccount(info) => {
let CreateAccount {
token,
username,
password,
} = &info;
let salt = SaltString::generate(&mut OsRng);
let params = scrypt::Params::new(11, 8, 1, 32).unwrap();
let hash = Scrypt
.hash_password_customized(password.as_bytes(), None, None, params, &salt)
.unwrap()
.to_string();
let mut id;
loop {
let mut tx = self.db.write_tx();
let Some(perms) = tx.remove(&self.db.account_tokens, token.to_string()) else {
let _ = replier.send(CreateAccountResp::InvalidToken).await;
println!("invalid token: {:?}", self.send.remote());
return;
};
if tx.has_key(&self.db.usernames, username.clone()) {
let _ = replier.send(CreateAccountResp::UsernameExists).await;
return;
}
id = rand::random();
while tx.has_key(&self.db.users, id) {
id = rand::random();
}
tx.insert(
&self.db.users,
&id,
&User {
username: username.clone(),
password_hash: hash.clone(),
bio: String::new(),
pfp: None,
server_perms: perms,
},
);
tx.insert(&self.db.usernames, username, &id);
if tx.commit() {
break;
}
}
println!("account created: \"{username}\"");
*self.state.write().await = ClientState::Authed(id);
let _ = replier.send(CreateAccountResp::Ok { id }).await;
}
ClientMsg::Login(info) => {
let Login { username, password } = &info;
let Some(id) = self.db.usernames.get(username) else {
let _ = replier.send(LoginResp::UnknownUsername).await;
return;
};
let Some(user) = self.db.users.get(&id) else {
panic!("invalid state! (should be a user)");
};
let hash = PasswordHash::new(&user.password_hash).unwrap();
if Scrypt.verify_password(password.as_bytes(), &hash).is_err() {
println!("invalid password: \"{username}\"");
let _ = replier.send(LoginResp::InvalidPassword).await;
return;
}
*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;
}
}
}
async fn disconnect(&self, reason: DisconnectReason) -> () {
match reason {
DisconnectReason::Closed | DisconnectReason::Timeout => (),
DisconnectReason::Other(e) => println!("connection issue: {e}"),
}
}
}
impl ClientState {
pub fn is_authed(&self) -> bool {
matches!(self, Self::Authed(_))
}
}

View File

@@ -1,5 +1,5 @@
use openworm::net::{ use openworm::net::{
ClientRequestMsg, RecvHandler, RequestId, SERVER_NAME, SendResult, ServerMsg, ServerRespMsg, ClientMsgInst, RecvHandler, RequestId, SERVER_NAME, SendResult, ServerMsg, ServerMsgInst,
recv_uni, send_uni, recv_uni, send_uni,
}; };
use quinn::{ use quinn::{
@@ -65,9 +65,9 @@ impl ClientSender {
} }
pub async fn send(&self, msg: impl Into<ServerMsg>) -> SendResult { pub async fn send(&self, msg: impl Into<ServerMsg>) -> SendResult {
let msg = ServerRespMsg { let msg = ServerMsgInst {
id: None, id: None,
msg: msg.into().into(), msg: msg.into(),
}; };
send_uni(&self.conn, msg).await send_uni(&self.conn, msg).await
} }
@@ -79,12 +79,12 @@ pub struct ClientReplier {
} }
impl ClientReplier { impl ClientReplier {
pub async fn send(&self, msg: impl Into<ServerMsg>) -> SendResult { pub async fn send(&self, msg: impl Into<ServerMsg>) {
let msg = ServerRespMsg { let msg = ServerMsgInst {
id: Some(self.req_id), id: Some(self.req_id),
msg: msg.into().into(), msg: msg.into(),
}; };
send_uni(&self.conn, msg).await let _ = send_uni(&self.conn, msg).await;
} }
} }
@@ -92,7 +92,7 @@ pub trait ConAccepter: Send + Sync + 'static {
fn accept( fn accept(
&self, &self,
send: ClientSender, send: ClientSender,
) -> impl Future<Output = impl RecvHandler<ClientRequestMsg>> + Send; ) -> impl Future<Output = impl RecvHandler<ClientMsgInst>> + Send;
} }
pub fn listen( pub fn listen(

View File

@@ -1,54 +0,0 @@
use crate::net::{
ClientMsg, ServerMsg,
data::{ClientMsgInst, ServerMsgInst},
};
impl From<ClientMsg> for ClientMsgInst {
fn from(value: ClientMsg) -> Self {
match value {
ClientMsg::CreateAccount(v) => Self::CreateAccountV0(v),
ClientMsg::RequestMsgs => Self::RequestMsgsV0,
ClientMsg::SendMsg(v) => Self::SendMsgV0(v),
ClientMsg::Login(v) => Self::LoginV0(v),
ClientMsg::RequestUsers(v) => Self::RequestUsersV0(v),
}
}
}
impl From<ClientMsgInst> for ClientMsg {
fn from(value: ClientMsgInst) -> Self {
match value {
ClientMsgInst::CreateAccountV0(v) => Self::CreateAccount(v),
ClientMsgInst::RequestMsgsV0 => Self::RequestMsgs,
ClientMsgInst::SendMsgV0(v) => Self::SendMsg(v),
ClientMsgInst::LoginV0(v) => Self::Login(v),
ClientMsgInst::RequestUsersV0(v) => Self::RequestUsers(v),
}
}
}
impl From<ServerMsg> for ServerMsgInst {
fn from(value: ServerMsg) -> Self {
match value {
ServerMsg::CreateAccountResp(v) => Self::CreateAccountRespV0(v),
ServerMsg::LoadMsg(v) => Self::LoadMsgV0(v),
ServerMsg::LoadMsgs(v) => Self::LoadMsgsV0(v),
ServerMsg::ServerError(v) => Self::ServerErrorV0(v),
ServerMsg::LoginResp(v) => Self::LoginRespV0(v),
ServerMsg::RequestUsersResp(v) => Self::RequestUsersRespV0(v),
}
}
}
impl From<ServerMsgInst> for ServerMsg {
fn from(value: ServerMsgInst) -> Self {
match value {
ServerMsgInst::CreateAccountRespV0(v) => Self::CreateAccountResp(v),
ServerMsgInst::LoadMsgV0(v) => Self::LoadMsg(v),
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

@@ -1,106 +1,110 @@
use rand::TryRng; use iris::core::util::HashSet;
#[repr(u32)] pub type UserId = u64;
#[derive(Debug, bitcode::Encode, bitcode::Decode)] pub type AccountToken = String;
pub enum ClientMsgInst {
CreateAccountV0(CreateAccountV0) = 0,
LoginV0(LoginV0) = 1,
RequestMsgsV0 = 2,
SendMsgV0(SendMsgV0) = 3,
RequestUsersV0(RequestUsersV0) = 4,
}
#[repr(u32)]
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
pub enum ServerMsgInst {
CreateAccountRespV0(CreateAccountRespV0) = 0,
LoginRespV0(LoginRespV0) = 1,
LoadMsgV0(LoadMsgV0) = 2,
LoadMsgsV0(Vec<LoadMsgV0>) = 3,
ServerErrorV0(ServerErrorV0) = 4,
RequestUsersRespV0(RequestUsersRespV0) = 5,
}
pub type UserIdV0 = u64;
#[derive(Debug, bitcode::Encode, bitcode::Decode)] #[derive(Debug, bitcode::Encode, bitcode::Decode)]
pub struct CreateAccountV0 { pub struct CreateAccount {
pub username: String, pub username: String,
pub password: String, pub password: String,
pub token: String, pub token: String,
} }
#[derive(Debug, bitcode::Encode, bitcode::Decode)] #[derive(Debug, bitcode::Encode, bitcode::Decode)]
pub struct LoginV0 { pub enum CreateAccountResp {
pub username: String, Ok { id: UserId },
pub password: String,
}
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
pub enum CreateAccountRespV0 {
Ok { id: UserIdV0 },
UsernameExists, UsernameExists,
InvalidToken, InvalidToken,
} }
#[derive(Debug, bitcode::Encode, bitcode::Decode)] #[derive(Debug, bitcode::Encode, bitcode::Decode)]
pub enum LoginRespV0 { pub struct Login {
Ok { id: UserIdV0 }, pub username: String,
pub password: String,
}
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
pub enum LoginResp {
Ok { id: UserId },
UnknownUsername, UnknownUsername,
InvalidPassword, InvalidPassword,
} }
#[derive(Debug, bitcode::Encode, bitcode::Decode)] #[derive(Debug, bitcode::Encode, bitcode::Decode)]
pub struct RequestUsersV0; pub struct RequestUsers;
#[derive(Debug, bitcode::Encode, bitcode::Decode)] #[derive(Debug, bitcode::Encode, bitcode::Decode)]
pub struct RequestUsersRespV0 { pub struct RequestUsersResp {
pub users: Vec<ServerUserV0>, pub users: Vec<ServerUser>,
} }
#[derive(Debug, bitcode::Encode, bitcode::Decode)] #[derive(Debug, bitcode::Encode, bitcode::Decode)]
pub struct ServerUserV0 { pub struct ServerUser {
pub id: UserIdV0, pub id: UserId,
pub username: String, pub username: String,
} }
#[derive(Debug, bitcode::Encode, bitcode::Decode)] #[derive(Debug, bitcode::Encode, bitcode::Decode)]
pub struct LoginKeyV0(Vec<u8>); pub struct RequestFriends;
impl LoginKeyV0 { #[derive(Debug, bitcode::Encode, bitcode::Decode)]
pub const BIT_LEN: usize = 1024; pub struct RequestFriendsResp {
pub const BYTE_LEN: usize = Self::BIT_LEN / 8; pub current: HashSet<UserId>,
pub incoming: HashSet<UserId>,
pub fn new() -> Self { pub outgoing: HashSet<UserId>,
let mut key = [0u8; Self::BYTE_LEN];
rand::rngs::SysRng
.try_fill_bytes(&mut key)
.expect("failed to generate random key");
Self(key.to_vec())
}
pub fn bytes(&self) -> &[u8] {
&self.0
}
}
impl From<Vec<u8>> for LoginKeyV0 {
fn from(value: Vec<u8>) -> Self {
Self(value)
}
}
#[derive(Debug, Clone, bitcode::Encode, bitcode::Decode)]
pub struct SendMsgV0 {
pub content: String,
}
#[derive(Debug, Clone, bitcode::Encode, bitcode::Decode)]
pub struct LoadMsgV0 {
pub content: String,
pub author: UserIdV0,
} }
#[derive(Debug, bitcode::Encode, bitcode::Decode)] #[derive(Debug, bitcode::Encode, bitcode::Decode)]
pub enum ServerErrorV0 { pub struct AddFriend {
NotLoggedIn, pub username: String,
NoPermission,
} }
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
pub enum AddFriendResp {
Ok,
UnknownUser,
CannotAddSelf,
AlreadySent,
AlreadyFriends,
}
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
pub struct RemoveFriend {
pub id: UserId,
}
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
pub struct AnswerFriendRequest {
pub id: UserId,
pub action: FriendRequestAction,
}
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
pub enum FriendRequestAction {
Accept,
Deny,
}
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
pub struct GenerateToken {
pub perms: ServerPerms,
}
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
pub struct GenerateTokenResp {
pub token: AccountToken,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, bitcode::Encode, bitcode::Decode)]
pub struct ServerPerms(u32);
impl ServerPerms {
pub const NONE: Self = Self(0);
pub const ACCOUNT_TOKENS: Self = Self(1 << 0);
pub const ALL: Self = Self(u32::MAX);
}
impl ServerPerms {
pub fn contains(&self, other: Self) -> bool {
(self.0 & other.0) == other.0
}
}
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
pub struct NotLoggedIn;
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
pub struct NoPermission;
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
pub struct AccountDeleted;

View File

@@ -1,11 +1,10 @@
mod conversion; mod data;
pub mod data;
mod msg; mod msg;
mod no_cert; mod no_cert;
mod request; mod request;
mod transfer; mod transfer;
pub use data::{ClientMsgInst, ServerMsgInst}; pub use data::*;
pub use msg::*; pub use msg::*;
pub use no_cert::*; pub use no_cert::*;
pub use request::*; pub use request::*;
@@ -13,8 +12,6 @@ pub use transfer::*;
pub const SERVER_NAME: &str = "openworm"; pub const SERVER_NAME: &str = "openworm";
pub type ServerResp<T> = Result<T, String>;
pub fn install_crypto_provider() { pub fn install_crypto_provider() {
quinn::rustls::crypto::ring::default_provider() quinn::rustls::crypto::ring::default_provider()
.install_default() .install_default()

View File

@@ -1,83 +1,53 @@
use super::data; use super::{RequestMsg, data::*};
#[derive(Debug)] msg_type!(ClientMsg: {
pub enum ClientMsg { 0: CreateAccount => CreateAccountResp,
CreateAccount(CreateAccount), 1: Login => LoginResp,
Login(Login), 2: RequestUsers => RequestUsersResp,
RequestMsgs, 3: RequestFriends => RequestFriendsResp,
SendMsg(SendMsg), 4: AddFriend => AddFriendResp,
RequestUsers(RequestUsers), 5: RemoveFriend,
} 6: AnswerFriendRequest,
7: GenerateToken => GenerateTokenResp,
});
msg_type!(ServerMsg: {
0: NotLoggedIn,
1: NoPermission,
2: AccountDeleted,
3: CreateAccountResp,
4: LoginResp,
5: RequestUsersResp,
6: RequestFriendsResp,
7: AddFriendResp,
8: GenerateTokenResp,
});
#[derive(Debug)] macro_rules! msg_type {
pub enum ServerMsg { ($msg:ident: {$($num:literal: $name:ident $(=> $resp:ident)?,)*}) => {
CreateAccountResp(CreateAccountResp), #[repr(u32)]
LoginResp(LoginResp), #[derive(Debug, bitcode::Encode, bitcode::Decode)]
LoadMsg(LoadMsg), pub enum $msg {$(
LoadMsgs(Vec<LoadMsg>), $name($name) = $num,
ServerError(ServerError), )*}
RequestUsersResp(RequestUsersResp), $(
} impl From<$name> for $msg {
fn from(value: $name) -> Self {
// TODO: a ton of this should really just be macros :sob: Self::$name(value)
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 {
Self::CreateAccount(value)
} }
}
impl From<Login> for ClientMsg {
fn from(value: Login) -> Self {
Self::Login(value)
} }
} $(
impl RequestMsg for $name {
impl From<RequestUsers> for ClientMsg { type Result = $resp;
fn from(value: RequestUsers) -> Self { fn result(msg: ServerMsg) -> Option<Self::Result> {
Self::RequestUsers(value) if let ServerMsg::$resp(res) = msg {
Some(res)
} else {
None
} }
}
impl From<ServerError> for ServerMsg {
fn from(value: ServerError) -> Self {
Self::ServerError(value)
} }
}
impl From<LoadMsg> for ServerMsg {
fn from(value: LoadMsg) -> Self {
Self::LoadMsg(value)
}
}
impl From<CreateAccountResp> for ServerMsg {
fn from(value: CreateAccountResp) -> Self {
Self::CreateAccountResp(value)
}
}
impl From<LoginResp> for ServerMsg {
fn from(value: LoginResp) -> Self {
Self::LoginResp(value)
}
}
impl From<RequestUsersResp> for ServerMsg {
fn from(value: RequestUsersResp) -> Self {
Self::RequestUsersResp(value)
} }
)?
)*
};
} }
use msg_type;

View File

@@ -19,54 +19,18 @@ impl RequestId {
} }
#[derive(Debug, bitcode::Encode, bitcode::Decode)] #[derive(Debug, bitcode::Encode, bitcode::Decode)]
pub struct ClientRequestMsg { pub struct ClientMsgInst {
pub id: RequestId, pub id: RequestId,
pub msg: ClientMsgInst, pub msg: ClientMsg,
} }
#[derive(Debug, bitcode::Encode, bitcode::Decode)] #[derive(Debug, bitcode::Encode, bitcode::Decode)]
pub struct ServerRespMsg { pub struct ServerMsgInst {
pub id: Option<RequestId>, pub id: Option<RequestId>,
pub msg: ServerMsgInst, pub msg: ServerMsg,
} }
pub trait RequestMsg: Into<ClientMsg> { pub trait RequestMsg: Into<ClientMsg> {
type Result; type Result;
fn result(msg: ServerMsg) -> Option<Self::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
}
}
}