ACCOUNT CREATION AND LOGIN
This commit is contained in:
1926
Cargo.lock
generated
1926
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
15
Cargo.toml
15
Cargo.toml
@@ -1,4 +1,4 @@
|
||||
cargo-features = ["codegen-backend"]
|
||||
# cargo-features = ["codegen-backend"]
|
||||
|
||||
[package]
|
||||
name = "openworm"
|
||||
@@ -23,11 +23,14 @@ clap = { version = "4.5.53", features = ["derive"] }
|
||||
scrypt = "0.11.0"
|
||||
ed25519-dalek = { version = "3.0.0-pre.2", features = ["rand_core"] }
|
||||
rand = { version = "0.10.0-rc.5", features = ["chacha", "sys_rng"] }
|
||||
keyring = { version = "3.6.3", features = ["apple-native", "sync-secret-service", "windows-native"] }
|
||||
keyring = { version = "4.0.0-rc.3" }
|
||||
bitcode = "0.6.9"
|
||||
dashmap = "6.1.0"
|
||||
fjall = "3.0.1"
|
||||
time = { version = "0.3.47", features = ["local-offset"] }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
derive_more = { version = "2.1.1", features = ["deref", "deref_mut"] }
|
||||
keyring-core = "0.7.2"
|
||||
|
||||
[[bin]]
|
||||
name = "openworm-client"
|
||||
@@ -37,8 +40,8 @@ path = "src/bin/client/main.rs"
|
||||
name = "openworm-server"
|
||||
path = "src/bin/server/main.rs"
|
||||
|
||||
[profile.dev]
|
||||
codegen-backend = "cranelift"
|
||||
# [profile.dev]
|
||||
# codegen-backend = "cranelift"
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 1
|
||||
# [profile.dev.package."*"]
|
||||
# opt-level = 1
|
||||
|
||||
BIN
src/bin/client/assets/orage.jpg
Normal file
BIN
src/bin/client/assets/orage.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 148 KiB |
@@ -1,48 +0,0 @@
|
||||
use openworm::{
|
||||
net::LoginKey,
|
||||
rsc::{DataDir, DataRsc},
|
||||
};
|
||||
|
||||
pub struct ClientData {
|
||||
pub dir: DataDir,
|
||||
pub cache: Cache,
|
||||
}
|
||||
|
||||
impl ClientData {
|
||||
pub fn load() -> Self {
|
||||
let dir = DataDir::default();
|
||||
Self {
|
||||
cache: dir.load(),
|
||||
dir,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save(&self) {
|
||||
self.dir.save(&self.cache);
|
||||
}
|
||||
|
||||
pub fn login_key(&self, user: &str) -> LoginKey {
|
||||
let entry = keyring::Entry::new("openworm", user).expect("failed to open keyring entry");
|
||||
if let Ok(secret) = entry.get_secret() {
|
||||
LoginKey::from(secret)
|
||||
} else {
|
||||
LoginKey::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, bitcode::Encode, bitcode::Decode)]
|
||||
pub struct Cache {
|
||||
pub ip: String,
|
||||
pub username: String,
|
||||
/// TODO: not store this as plain string?
|
||||
/// need to figure out crypto stuff
|
||||
/// or store session token
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
impl DataRsc for Cache {
|
||||
fn path() -> &'static str {
|
||||
"client_data"
|
||||
}
|
||||
}
|
||||
42
src/bin/client/data/mod.rs
Normal file
42
src/bin/client/data/mod.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
use openworm::rsc::{DataDir, DataGuard};
|
||||
|
||||
mod ver;
|
||||
|
||||
pub struct ClientData {
|
||||
pub data: DataDir,
|
||||
}
|
||||
|
||||
pub type AccountList = ver::AccountListV0;
|
||||
pub type AccountInfo = ver::AccountInfoV0;
|
||||
pub type ServerList = ver::ServerListV0;
|
||||
pub type ServerInfo = ver::ServerInfoV0;
|
||||
|
||||
impl ClientData {
|
||||
pub fn load() -> Self {
|
||||
let data = DataDir::new(None);
|
||||
Self { data }
|
||||
}
|
||||
|
||||
pub fn create_account(&self, server: ServerInfo, info: AccountInfo, password: &str) {
|
||||
keyring::use_native_store(true).unwrap();
|
||||
let user_path = info.path();
|
||||
let entry =
|
||||
keyring_core::Entry::new("openworm", &user_path).expect("failed to open keyring entry");
|
||||
entry.set_password(password).unwrap();
|
||||
self.data
|
||||
.load::<ServerList>()
|
||||
.insert(info.url.clone(), server);
|
||||
self.data.load::<AccountList>().push(info);
|
||||
}
|
||||
|
||||
pub fn password(&self, info: &AccountInfo) -> String {
|
||||
let user_path = info.path();
|
||||
let entry =
|
||||
keyring_core::Entry::new("openworm", &user_path).expect("failed to open keyring entry");
|
||||
entry.get_password().unwrap()
|
||||
}
|
||||
|
||||
pub fn accounts(&self) -> DataGuard<AccountList> {
|
||||
self.data.load::<AccountList>()
|
||||
}
|
||||
}
|
||||
58
src/bin/client/data/ver.rs
Normal file
58
src/bin/client/data/ver.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use iris::core::util::HashMap;
|
||||
use openworm::rsc::DataRsc;
|
||||
|
||||
#[derive(Default, serde::Serialize, serde::Deserialize, Deref, DerefMut)]
|
||||
pub struct AccountListV0(Vec<AccountInfoV0>);
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct AccountInfoV0 {
|
||||
pub url: String,
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
impl AccountInfoV0 {
|
||||
pub fn path(&self) -> String {
|
||||
self.username.clone() + "@" + &self.url
|
||||
}
|
||||
}
|
||||
|
||||
impl DataRsc for AccountListV0 {
|
||||
fn name() -> &'static str {
|
||||
"account_list"
|
||||
}
|
||||
|
||||
fn parse_version(text: &str, version: u32) -> Result<Self, String> {
|
||||
match version {
|
||||
0 => ron::from_str(text).map_err(|e| e.to_string()),
|
||||
_ => Err(format!("unknown version {version}")),
|
||||
}
|
||||
}
|
||||
|
||||
fn version() -> u32 {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, serde::Serialize, serde::Deserialize, Deref, DerefMut)]
|
||||
pub struct ServerListV0(HashMap<String, ServerInfoV0>);
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ServerInfoV0 {
|
||||
pub cert_hex: String,
|
||||
}
|
||||
|
||||
impl DataRsc for ServerListV0 {
|
||||
fn name() -> &'static str {
|
||||
"server_list"
|
||||
}
|
||||
|
||||
fn parse_version(text: &str, version: u32) -> Result<Self, String> {
|
||||
match version {
|
||||
0 => ron::from_str(text).map_err(|e| e.to_string()),
|
||||
_ => Err(format!("unknown version {version}")),
|
||||
}
|
||||
}
|
||||
|
||||
fn version() -> u32 {
|
||||
0
|
||||
}
|
||||
}
|
||||
@@ -62,11 +62,12 @@ impl DefaultAppState for Client {
|
||||
.stack()
|
||||
.set_root(rsc, &mut ui_state);
|
||||
|
||||
ui::start(rsc).set_ptr(main_ui, rsc);
|
||||
let data = ClientData::load();
|
||||
ui::start(rsc, &data).set_ptr(main_ui, rsc);
|
||||
|
||||
Self {
|
||||
ui_state,
|
||||
data: ClientData::load(),
|
||||
data,
|
||||
state: Default::default(),
|
||||
main_ui,
|
||||
notif,
|
||||
@@ -90,7 +91,6 @@ impl DefaultAppState for Client {
|
||||
|
||||
fn exit(&mut self, _rsc: &mut DefaultRsc<Self>, _render: &mut UiRenderState) {
|
||||
self.state.exit();
|
||||
self.data.save();
|
||||
}
|
||||
|
||||
fn window_event(
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::ClientEvent;
|
||||
use dashmap::DashMap;
|
||||
use openworm::net::{
|
||||
ClientMsg, ClientRequestMsg, CreateAccount, CreateAccountResp, RecvHandler, RequestId,
|
||||
SERVER_NAME, ServerMsg, ServerRespMsg, SkipServerVerification, recv_uni, send_uni,
|
||||
ClientMsg, ClientRequestMsg, CreateAccount, CreateAccountResp, Login, LoginResp, RecvHandler,
|
||||
RequestId, SERVER_NAME, ServerMsg, ServerRespMsg, SkipServerVerification, recv_uni, send_uni,
|
||||
};
|
||||
use quinn::{
|
||||
ClientConfig, Connection, Endpoint, IdleTimeout, TransportConfig,
|
||||
@@ -10,6 +10,7 @@ use quinn::{
|
||||
};
|
||||
use std::{
|
||||
net::{Ipv6Addr, SocketAddr, SocketAddrV6, ToSocketAddrs},
|
||||
str::FromStr,
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
@@ -83,7 +84,7 @@ impl RequestMsg for CreateAccount {
|
||||
type Result = CreateAccountResp;
|
||||
|
||||
fn result(msg: ServerMsg) -> Option<Self::Result> {
|
||||
if let ServerMsg::CreateAccount(res) = msg {
|
||||
if let ServerMsg::CreateAccountResp(res) = msg {
|
||||
Some(res)
|
||||
} else {
|
||||
None
|
||||
@@ -91,19 +92,41 @@ impl RequestMsg for CreateAccount {
|
||||
}
|
||||
}
|
||||
|
||||
async fn connection_cert(addr: SocketAddr, cert: CertificateDer) -> NetResult<Connection> {
|
||||
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<'_>,
|
||||
) -> NetResult<(Endpoint, Connection)> {
|
||||
let mut roots = quinn::rustls::RootCertStore::empty();
|
||||
roots.add(cert);
|
||||
roots
|
||||
.add(cert)
|
||||
.map_err(|e| format!("Invalid Certificate: {e:?}"))?;
|
||||
let client_crypto = quinn::rustls::ClientConfig::builder()
|
||||
.with_root_certificates(roots)
|
||||
.with_no_client_auth();
|
||||
let client_config = ClientConfig::new(Arc::new(QuicClientConfig::try_from(client_crypto)?));
|
||||
let mut endpoint = quinn::Endpoint::client(SocketAddr::from_str("[::]:0").unwrap())?;
|
||||
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())?;
|
||||
endpoint.set_default_client_config(client_config);
|
||||
endpoint
|
||||
.connect(addr, SERVER_NAME)?
|
||||
let conn = endpoint
|
||||
.connect(addr, SERVER_NAME)
|
||||
.map_err(|e| e.to_string())?
|
||||
.await
|
||||
.map_err(|e| format!("failed to connect: {}", e))
|
||||
.map_err(|e| format!("failed to connect: {}", e))?;
|
||||
Ok((endpoint, conn))
|
||||
}
|
||||
|
||||
async fn connection_no_cert(addr: SocketAddr) -> NetResult<(Endpoint, Connection)> {
|
||||
@@ -148,7 +171,7 @@ impl NetHandle {
|
||||
.map_err(|e| e.to_string())?
|
||||
.next()
|
||||
.ok_or("no addresses found".to_string())?;
|
||||
let (endpoint, conn) = connection_cert(addr).await?;
|
||||
let (endpoint, conn) = connection_cert(addr, cert).await?;
|
||||
let conn_ = conn.clone();
|
||||
|
||||
let mut req_id = RequestId::first();
|
||||
|
||||
@@ -2,3 +2,4 @@ use super::*;
|
||||
|
||||
pub const MODAL_BG: UiColor = UiColor::BLACK.brighter(0.05);
|
||||
pub const GREEN: UiColor = UiColor::rgb(0, 150, 0);
|
||||
pub const DARK: UiColor = UiColor::BLACK.brighter(0.02);
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
use openworm::net::{CreateAccount, CreateAccountResp};
|
||||
use openworm::net::{CreateAccount, CreateAccountResp, Login, LoginResp};
|
||||
|
||||
use crate::net::{ConnectInfo, NetHandle};
|
||||
use crate::{
|
||||
data::{AccountInfo, ClientData, ServerInfo, ServerList},
|
||||
net::{ConnectInfo, NetHandle},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
pub fn start(rsc: &mut Rsc) -> WeakWidget {
|
||||
pub fn start(rsc: &mut Rsc, data: &ClientData) -> WeakWidget {
|
||||
let mut accounts = Span::empty(Dir::DOWN);
|
||||
|
||||
let accts = data.accounts();
|
||||
if accts.is_empty() {
|
||||
accounts.push(
|
||||
wtext("no accounts")
|
||||
.size(20)
|
||||
@@ -15,6 +20,82 @@ pub fn start(rsc: &mut Rsc) -> WeakWidget {
|
||||
.height(60)
|
||||
.add_strong(rsc),
|
||||
);
|
||||
} else {
|
||||
for account in accts.iter() {
|
||||
let button = Button::new_fg(
|
||||
wtext(&account.username)
|
||||
.size(20)
|
||||
.center_text()
|
||||
.height(60)
|
||||
.add(rsc),
|
||||
color::DARK,
|
||||
rsc,
|
||||
)
|
||||
.add(rsc);
|
||||
let account = account.clone();
|
||||
let cert_hex = data
|
||||
.data
|
||||
.load::<ServerList>()
|
||||
.get(&account.url)
|
||||
.unwrap()
|
||||
.cert_hex
|
||||
.clone();
|
||||
let cert = decode_hex(&cert_hex).unwrap();
|
||||
keyring::use_native_store(true).unwrap();
|
||||
rsc.events.register(button, Submit, move |ctx, rsc| {
|
||||
let account = account.clone();
|
||||
let cert = cert.clone();
|
||||
let password = ctx.state.data.password(&account);
|
||||
rsc.spawn_task(async move |mut ctx| {
|
||||
let mut fail = |reason: &str| {
|
||||
let reason = reason.to_string();
|
||||
ctx.update(move |ctx, rsc| {
|
||||
rsc[ctx.notif].inner = Some(werror(&reason, rsc));
|
||||
})
|
||||
};
|
||||
let net = match NetHandle::connect(
|
||||
async |msg| {
|
||||
println!("msg recv :joy:");
|
||||
},
|
||||
ConnectInfo {
|
||||
url: account.url.clone(),
|
||||
cert,
|
||||
},
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
return fail(&e);
|
||||
}
|
||||
};
|
||||
|
||||
let Ok(resp) = net
|
||||
.request(Login {
|
||||
username: account.username.clone(),
|
||||
password: password.clone(),
|
||||
})
|
||||
.await
|
||||
else {
|
||||
return fail("failed to create account");
|
||||
};
|
||||
let id = match resp {
|
||||
LoginResp::Ok { id } => id,
|
||||
LoginResp::UnknownUsername => {
|
||||
return fail("unknown username");
|
||||
}
|
||||
LoginResp::InvalidPassword => {
|
||||
return fail("invalid password");
|
||||
}
|
||||
};
|
||||
ctx.update(move |ctx, rsc| {
|
||||
main_view(rsc).set_ptr(ctx.main_ui, rsc);
|
||||
});
|
||||
});
|
||||
});
|
||||
accounts.push(button.add_strong(rsc));
|
||||
}
|
||||
}
|
||||
|
||||
let connect = Button::submit("connect", rsc);
|
||||
let create = Button::submit("create", rsc);
|
||||
@@ -47,41 +128,48 @@ pub fn create_account(rsc: &mut Rsc) -> WeakWidget {
|
||||
|
||||
let create = Button::submit("create", rsc);
|
||||
rsc.events.register(create, Submit, move |ctx, rsc| {
|
||||
let url = rsc[url].content();
|
||||
let token = rsc[token].content();
|
||||
let cert = rsc[cert].content();
|
||||
let Ok(cert) = decode_hex(&cert) else {
|
||||
let url = rsc[url].content().trim().to_string();
|
||||
let token = rsc[token].content().trim().to_string();
|
||||
let cert_hex = rsc[cert].content().trim().to_string();
|
||||
let Some(cert) = decode_hex(&cert_hex) else {
|
||||
rsc[ctx.state.notif].inner = Some(werror("Invalid certificate hex", rsc));
|
||||
return;
|
||||
};
|
||||
let username = rsc[username].content();
|
||||
let password = rsc[password].content();
|
||||
let login_key = ctx.state.data.login_key(&username);
|
||||
|
||||
create.disable(rsc);
|
||||
rsc.spawn_task(async move |mut ctx| {
|
||||
let mut fail = move |reason| {
|
||||
let mut fail = |reason: &str| {
|
||||
let reason = reason.to_string();
|
||||
ctx.update(move |ctx, rsc| {
|
||||
rsc[ctx.notif].inner = Some(werror(reason, rsc));
|
||||
rsc[ctx.notif].inner = Some(werror(&reason, rsc));
|
||||
create.enable(rsc);
|
||||
})
|
||||
};
|
||||
let Ok(net) = NetHandle::connect(
|
||||
keyring::use_native_store(true).unwrap();
|
||||
let net = match NetHandle::connect(
|
||||
async |msg| {
|
||||
println!("msg recv :joy:");
|
||||
},
|
||||
ConnectInfo { url, cert },
|
||||
ConnectInfo {
|
||||
url: url.clone(),
|
||||
cert,
|
||||
},
|
||||
)
|
||||
.await
|
||||
else {
|
||||
return fail("failed to connect");
|
||||
{
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
return fail(&e);
|
||||
}
|
||||
};
|
||||
|
||||
let Ok(resp) = net
|
||||
.request(CreateAccount {
|
||||
username,
|
||||
password,
|
||||
username: username.clone(),
|
||||
password: password.clone(),
|
||||
token,
|
||||
login_key,
|
||||
})
|
||||
.await
|
||||
else {
|
||||
@@ -96,7 +184,14 @@ pub fn create_account(rsc: &mut Rsc) -> WeakWidget {
|
||||
return fail("invalid account token");
|
||||
}
|
||||
};
|
||||
println!("account created!!!!");
|
||||
ctx.update(move |ctx, rsc| {
|
||||
main_view(rsc).set_ptr(ctx.main_ui, rsc);
|
||||
ctx.data.create_account(
|
||||
ServerInfo { cert_hex },
|
||||
AccountInfo { url, username },
|
||||
&password,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -115,115 +210,12 @@ pub fn create_account(rsc: &mut Rsc) -> WeakWidget {
|
||||
.add(rsc)
|
||||
}
|
||||
|
||||
pub fn decode_hex(s: &str) -> Result<Vec<u8>, std::num::ParseIntError> {
|
||||
pub fn decode_hex(s: &str) -> Option<Vec<u8>> {
|
||||
if !s.len().is_multiple_of(2) {
|
||||
return None;
|
||||
}
|
||||
(0..s.len())
|
||||
.step_by(2)
|
||||
.map(|i| u8::from_str_radix(&s[i..i + 2], 16))
|
||||
.map(|i| u8::from_str_radix(&s[i..i + 2], 16).ok())
|
||||
.collect()
|
||||
}
|
||||
|
||||
// pub fn connect_screen(client: &mut Client, ui: &mut Ui, state: &UiState) -> WeakWidget {
|
||||
// let Client { data, proxy, .. } = client;
|
||||
// let ip = field_widget(&data.ip, "ip", ui);
|
||||
// let ip_ = ip.clone();
|
||||
// let handle = AppHandle {
|
||||
// proxy: proxy.clone(),
|
||||
// window: state.window.clone(),
|
||||
// };
|
||||
//
|
||||
// let submit = Button::submit("connect", ui);
|
||||
//
|
||||
// submit.on(Submit, move |ctx| {
|
||||
// let ClientState::Connect(state) = &mut ctx.state.state else {
|
||||
// return;
|
||||
// };
|
||||
// let ip = ip_.get().content();
|
||||
// state.handle = Some(connect(handle.clone(), ConnectInfo { ip }));
|
||||
// });
|
||||
//
|
||||
// (
|
||||
// wtext("connect to a server")
|
||||
// .text_align(Align::CENTER)
|
||||
// .size(30),
|
||||
// field_box(
|
||||
// // NOTE: should probably do this on submit
|
||||
// ip.on(Edited, |ctx| {
|
||||
// ctx.state.data.ip = ctx.widget.get().content();
|
||||
// })
|
||||
// .add(ui),
|
||||
// ui,
|
||||
// ),
|
||||
// submit,
|
||||
// )
|
||||
// .span(Dir::DOWN)
|
||||
// .gap(10)
|
||||
// .pad(15)
|
||||
// .background(rect(Color::BLACK.brighter(0.2)).radius(15))
|
||||
// .width(400)
|
||||
// .align(Align::CENTER)
|
||||
// .add(ui)
|
||||
// }
|
||||
|
||||
// pub fn login_screen(client: &mut Client, ui: &mut Ui) -> WeakWidget {
|
||||
// let Client { data, .. } = client;
|
||||
// let username = field_widget(&data.username, "username", ui);
|
||||
// let password = field_widget(&data.password, "password", ui);
|
||||
// let username_ = username.clone();
|
||||
// let password_ = password.clone();
|
||||
// let submit = Button::submit("login", ui);
|
||||
// submit
|
||||
// .on(move |client, _ui| {
|
||||
// let ClientState::Login(state) = &mut client.state else {
|
||||
// return;
|
||||
// };
|
||||
// let username = username_.get().content();
|
||||
// let password = password_.get().content();
|
||||
// state.handle.send(ClientMsg::Login { username, password });
|
||||
// })
|
||||
// .add(ui);
|
||||
// let username_ = username.clone();
|
||||
// let password_ = password.clone();
|
||||
// let create_button = Button::submit(
|
||||
// "create account",
|
||||
// move |client, _ui| {
|
||||
// let ClientState::Login(state) = &mut client.state else {
|
||||
// return;
|
||||
// };
|
||||
// let username = username_.get().content();
|
||||
// let password = password_.get().content();
|
||||
// state
|
||||
// .handle
|
||||
// .send(ClientMsg::CreateAccount { username, password });
|
||||
// },
|
||||
// ui,
|
||||
// );
|
||||
// create_button.on()
|
||||
// (
|
||||
// wtext("login to server").text_align(Align::CENTER).size(30),
|
||||
// field_box(
|
||||
// username
|
||||
// .on(Edited, |ctx| {
|
||||
// ctx.state.data.username = ctx.widget.get().content();
|
||||
// })
|
||||
// .add(ui),
|
||||
// ui,
|
||||
// ),
|
||||
// field_box(
|
||||
// password
|
||||
// .on(Edited, |ctx| {
|
||||
// ctx.state.data.password = ctx.widget.get().content();
|
||||
// })
|
||||
// .add(ui),
|
||||
// ui,
|
||||
// ),
|
||||
// submit,
|
||||
// create_button,
|
||||
// )
|
||||
// .span(Dir::DOWN)
|
||||
// .gap(10)
|
||||
// .pad(15)
|
||||
// .background(rect(Color::BLACK.brighter(0.2)).radius(15))
|
||||
// .width(400)
|
||||
// .align(Align::CENTER)
|
||||
// .add(ui)
|
||||
// }
|
||||
|
||||
@@ -38,7 +38,7 @@ pub struct Button {
|
||||
}
|
||||
|
||||
impl Button {
|
||||
pub fn new(text: &str, color: UiColor, rsc: &mut Rsc) -> Self {
|
||||
pub fn new_fg(fg: WeakWidget, color: UiColor, rsc: &mut Rsc) -> Self {
|
||||
let rect = rect(color).radius(15).add(rsc);
|
||||
let enabled = rsc.create_state(rect, true);
|
||||
let root = rect
|
||||
@@ -58,7 +58,7 @@ impl Button {
|
||||
rsc[ctx.widget].color = color;
|
||||
})
|
||||
.height(60)
|
||||
.foreground(wtext(text).size(25).text_align(Align::CENTER))
|
||||
.foreground(fg)
|
||||
.add(rsc);
|
||||
rect.on(CursorSense::click(), move |ctx, rsc: &mut Rsc| {
|
||||
if !rsc[enabled] {
|
||||
@@ -76,6 +76,14 @@ impl Button {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(text: &str, color: UiColor, rsc: &mut Rsc) -> Self {
|
||||
Self::new_fg(
|
||||
wtext(text).size(25).text_align(Align::CENTER).add(rsc),
|
||||
color,
|
||||
rsc,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn submit(text: &str, rsc: &mut Rsc) -> Self {
|
||||
Self::new(text, color::GREEN, rsc)
|
||||
}
|
||||
|
||||
@@ -7,14 +7,14 @@ use net::{ClientSender, ConAccepter, listen};
|
||||
use openworm::{
|
||||
net::{
|
||||
ClientMsg, ClientRequestMsg, CreateAccount, CreateAccountResp, DisconnectReason, LoadMsg,
|
||||
RecvHandler, ServerError, ServerMsg, install_crypto_provider,
|
||||
Login, LoginResp, RecvHandler, ServerError, ServerMsg, install_crypto_provider,
|
||||
},
|
||||
rsc::DataDir,
|
||||
};
|
||||
use rand::distr::{Alphanumeric, SampleString};
|
||||
use scrypt::{
|
||||
Scrypt,
|
||||
password_hash::{PasswordHasher, SaltString, rand_core::OsRng},
|
||||
password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString, rand_core::OsRng},
|
||||
};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
@@ -41,9 +41,8 @@ fn main() {
|
||||
|
||||
#[tokio::main]
|
||||
pub async fn run_server(port: u16) {
|
||||
let dir = DataDir::default();
|
||||
let path = dir.get().join("server");
|
||||
let db = Db::open(path.join("db"));
|
||||
let dir = DataDir::new(Some("server"));
|
||||
let db = Db::open(dir.path.join("db"));
|
||||
let handler = ServerListener {
|
||||
senders: Default::default(),
|
||||
count: 0.into(),
|
||||
@@ -53,7 +52,7 @@ pub async fn run_server(port: u16) {
|
||||
let token = account_token(&db, ServerPerms::ACCOUNT_TOKENS);
|
||||
println!("no users found, token for admin: {token}");
|
||||
}
|
||||
let (endpoint, handle) = listen(port, &path, handler);
|
||||
let (endpoint, handle) = listen(port, &dir.path, handler);
|
||||
let _ = ctrl_c().await;
|
||||
println!("stopping server");
|
||||
println!("closing connections...");
|
||||
@@ -171,7 +170,6 @@ impl RecvHandler<ClientRequestMsg> for ClientHandler {
|
||||
token,
|
||||
username,
|
||||
password,
|
||||
login_key,
|
||||
} = &info;
|
||||
let salt = SaltString::generate(&mut OsRng);
|
||||
let params = scrypt::Params::new(11, 8, 1, 32).unwrap();
|
||||
@@ -214,24 +212,26 @@ impl RecvHandler<ClientRequestMsg> for ClientHandler {
|
||||
println!("account created: \"{username}\"");
|
||||
*self.state.write().await = ClientState::Authed(id);
|
||||
let _ = replier.send(CreateAccountResp::Ok { id }).await;
|
||||
} // ClientMsgType::Login { username, password } => {
|
||||
// let Some(id) = self.db.usernames.get(&username) else {
|
||||
// let _ = self.send.send(ServerError::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 _ = self.send.send(ServerError::InvalidPassword).await;
|
||||
// return;
|
||||
// }
|
||||
// println!("login: \"{username}\"");
|
||||
// *self.state.write().await = ClientState::Authed(id);
|
||||
// let _ = self.send.send(ServerMsgType::Login { username }).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;
|
||||
}
|
||||
println!("login: \"{username}\"");
|
||||
*self.state.write().await = ClientState::Authed(id);
|
||||
let _ = replier.send(LoginResp::Ok { id }).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ pub fn init_endpoint(port: u16, data_path: &Path) -> Endpoint {
|
||||
};
|
||||
print!("cert hex: ");
|
||||
for x in cert.iter() {
|
||||
print!("{:x}", x);
|
||||
print!("{:02x}", x);
|
||||
}
|
||||
println!();
|
||||
let server_config = ServerConfig::with_single_cert(vec![cert], key).unwrap();
|
||||
|
||||
@@ -9,6 +9,7 @@ impl From<ClientMsg> for ClientMsgInst {
|
||||
ClientMsg::CreateAccount(v) => Self::CreateAccountV0(v),
|
||||
ClientMsg::RequestMsgs => Self::RequestMsgsV0,
|
||||
ClientMsg::SendMsg(v) => Self::SendMsgV0(v),
|
||||
ClientMsg::Login(v) => Self::LoginV0(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +20,7 @@ impl From<ClientMsgInst> for ClientMsg {
|
||||
ClientMsgInst::CreateAccountV0(v) => Self::CreateAccount(v),
|
||||
ClientMsgInst::RequestMsgsV0 => Self::RequestMsgs,
|
||||
ClientMsgInst::SendMsgV0(v) => Self::SendMsg(v),
|
||||
ClientMsgInst::LoginV0(v) => Self::Login(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,10 +28,11 @@ impl From<ClientMsgInst> for ClientMsg {
|
||||
impl From<ServerMsg> for ServerMsgInst {
|
||||
fn from(value: ServerMsg) -> Self {
|
||||
match value {
|
||||
ServerMsg::CreateAccount(v) => Self::CreateAccountV0(v),
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,10 +40,11 @@ impl From<ServerMsg> for ServerMsgInst {
|
||||
impl From<ServerMsgInst> for ServerMsg {
|
||||
fn from(value: ServerMsgInst) -> Self {
|
||||
match value {
|
||||
ServerMsgInst::CreateAccountV0(v) => Self::CreateAccount(v),
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,17 +4,19 @@ use rand::TryRng;
|
||||
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
|
||||
pub enum ClientMsgInst {
|
||||
CreateAccountV0(CreateAccountV0) = 0,
|
||||
RequestMsgsV0 = 1,
|
||||
SendMsgV0(SendMsgV0) = 2,
|
||||
LoginV0(LoginV0) = 1,
|
||||
RequestMsgsV0 = 2,
|
||||
SendMsgV0(SendMsgV0) = 3,
|
||||
}
|
||||
|
||||
#[repr(u32)]
|
||||
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
|
||||
pub enum ServerMsgInst {
|
||||
CreateAccountV0(CreateAccountRespV0) = 0,
|
||||
LoadMsgV0(LoadMsgV0) = 1,
|
||||
LoadMsgsV0(Vec<LoadMsgV0>) = 2,
|
||||
ServerErrorV0(ServerErrorV0) = 3,
|
||||
CreateAccountRespV0(CreateAccountRespV0) = 0,
|
||||
LoginRespV0(LoginRespV0) = 1,
|
||||
LoadMsgV0(LoadMsgV0) = 2,
|
||||
LoadMsgsV0(Vec<LoadMsgV0>) = 3,
|
||||
ServerErrorV0(ServerErrorV0) = 4,
|
||||
}
|
||||
|
||||
pub type UserIdV0 = u64;
|
||||
@@ -24,7 +26,12 @@ pub struct CreateAccountV0 {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
pub token: String,
|
||||
pub login_key: LoginKeyV0,
|
||||
}
|
||||
|
||||
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
|
||||
pub struct LoginV0 {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
|
||||
@@ -34,6 +41,13 @@ pub enum CreateAccountRespV0 {
|
||||
InvalidToken,
|
||||
}
|
||||
|
||||
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
|
||||
pub enum LoginRespV0 {
|
||||
Ok { id: UserIdV0 },
|
||||
UnknownUsername,
|
||||
InvalidPassword,
|
||||
}
|
||||
|
||||
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
|
||||
pub struct LoginKeyV0(Vec<u8>);
|
||||
impl LoginKeyV0 {
|
||||
@@ -47,6 +61,10 @@ impl LoginKeyV0 {
|
||||
.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 {
|
||||
|
||||
@@ -3,13 +3,15 @@ use super::data::*;
|
||||
#[derive(Debug)]
|
||||
pub enum ClientMsg {
|
||||
CreateAccount(CreateAccount),
|
||||
Login(Login),
|
||||
RequestMsgs,
|
||||
SendMsg(SendMsg),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ServerMsg {
|
||||
CreateAccount(CreateAccountResp),
|
||||
CreateAccountResp(CreateAccountResp),
|
||||
LoginResp(LoginResp),
|
||||
LoadMsg(LoadMsg),
|
||||
LoadMsgs(Vec<LoadMsg>),
|
||||
ServerError(ServerError),
|
||||
@@ -21,6 +23,8 @@ 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;
|
||||
|
||||
impl From<CreateAccount> for ClientMsg {
|
||||
@@ -29,6 +33,12 @@ impl From<CreateAccount> for ClientMsg {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Login> for ClientMsg {
|
||||
fn from(value: Login) -> Self {
|
||||
Self::Login(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ServerError> for ServerMsg {
|
||||
fn from(value: ServerError) -> Self {
|
||||
Self::ServerError(value)
|
||||
@@ -43,6 +53,12 @@ impl From<LoadMsg> for ServerMsg {
|
||||
|
||||
impl From<CreateAccountResp> for ServerMsg {
|
||||
fn from(value: CreateAccountResp) -> Self {
|
||||
Self::CreateAccount(value)
|
||||
Self::CreateAccountResp(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LoginResp> for ServerMsg {
|
||||
fn from(value: LoginResp) -> Self {
|
||||
Self::LoginResp(value)
|
||||
}
|
||||
}
|
||||
|
||||
123
src/rsc.rs
123
src/rsc.rs
@@ -1,50 +1,103 @@
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
io::Write,
|
||||
path::Path,
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use directories_next::ProjectDirs;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DataDir {
|
||||
dirs: ProjectDirs,
|
||||
}
|
||||
|
||||
impl Default for DataDir {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
dirs: ProjectDirs::from("", "", "openworm").unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DataRsc: bitcode::Encode + bitcode::DecodeOwned + Default {
|
||||
fn path() -> &'static str;
|
||||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
impl DataDir {
|
||||
pub fn get(&self) -> &Path {
|
||||
self.dirs.data_local_dir()
|
||||
pub fn new(dir: Option<&str>) -> Self {
|
||||
let dirs = ProjectDirs::from("", "", "openworm").unwrap();
|
||||
let mut path = dirs.data_local_dir().to_path_buf();
|
||||
if let Some(dir) = dir {
|
||||
path = path.join(dir);
|
||||
}
|
||||
Self { path }
|
||||
}
|
||||
}
|
||||
|
||||
pub trait DataRsc: serde::Serialize + Default {
|
||||
fn name() -> &'static str;
|
||||
fn path() -> String {
|
||||
Self::name().to_string() + ".ron"
|
||||
}
|
||||
fn parse_version(text: &str, version: u32) -> Result<Self, String>;
|
||||
fn version() -> u32;
|
||||
}
|
||||
|
||||
pub struct DataGuard<T: DataRsc> {
|
||||
path: PathBuf,
|
||||
val: T,
|
||||
}
|
||||
|
||||
impl DataDir {
|
||||
pub fn load<T: DataRsc>(&self) -> DataGuard<T> {
|
||||
let path = self.path.join(T::path());
|
||||
let invalid = |info: &str| {
|
||||
println!("warning: invalid config @ {path:?}: {info}");
|
||||
DataGuard {
|
||||
path: path.clone(),
|
||||
val: T::default(),
|
||||
}
|
||||
};
|
||||
match fs::read_to_string(&path) {
|
||||
Ok(text) => {
|
||||
let mut lines = text.lines();
|
||||
let Some(first) = lines.next() else {
|
||||
return invalid("empty file");
|
||||
};
|
||||
let version_str: String = first
|
||||
.chars()
|
||||
.skip_while(|c| *c != 'v')
|
||||
.skip(1)
|
||||
.take_while(|c| !c.is_whitespace())
|
||||
.collect();
|
||||
let Ok(version): Result<u32, _> = version_str.parse() else {
|
||||
return invalid("invalid version");
|
||||
};
|
||||
let text: String = lines.collect();
|
||||
match T::parse_version(&text, version) {
|
||||
Ok(val) => DataGuard { path, val },
|
||||
Err(e) => invalid(&e),
|
||||
}
|
||||
}
|
||||
Err(_) => DataGuard {
|
||||
path,
|
||||
val: T::default(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: DataRsc> std::ops::Deref for DataGuard<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.val
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: DataRsc> std::ops::DerefMut for DataGuard<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.val
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: DataRsc> Drop for DataGuard<T> {
|
||||
fn drop(&mut self) {
|
||||
let dir = self.path.parent().unwrap();
|
||||
fs::create_dir_all(dir).unwrap();
|
||||
let mut file = File::create(dir.join(T::path())).unwrap();
|
||||
let ron = ron::to_string(&self.val).unwrap();
|
||||
let data = format!("// v{}\n{}\n", T::version(), ron);
|
||||
if let Err(e) = file.write_all(data.as_bytes()) {
|
||||
println!("Failed to write config @ {:?}: {e}", self.path);
|
||||
}
|
||||
|
||||
pub fn load<T: DataRsc>(&self) -> T {
|
||||
let path = self.get().join(T::path());
|
||||
match fs::read(path) {
|
||||
Ok(bytes) => match bitcode::decode(&bytes) {
|
||||
Ok(data) => data,
|
||||
Err(_) => todo!(),
|
||||
},
|
||||
Err(_) => T::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save<T: DataRsc>(&self, data: &T) {
|
||||
let dir = self.get();
|
||||
fs::create_dir_all(dir).unwrap();
|
||||
let mut file = File::create(dir.join(T::path())).unwrap();
|
||||
// TODO: used to use encode_into_std_write from bincode here
|
||||
let data = bitcode::encode(data);
|
||||
file.write_all(&data).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user