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]
|
[package]
|
||||||
name = "openworm"
|
name = "openworm"
|
||||||
@@ -23,11 +23,14 @@ clap = { version = "4.5.53", features = ["derive"] }
|
|||||||
scrypt = "0.11.0"
|
scrypt = "0.11.0"
|
||||||
ed25519-dalek = { version = "3.0.0-pre.2", features = ["rand_core"] }
|
ed25519-dalek = { version = "3.0.0-pre.2", features = ["rand_core"] }
|
||||||
rand = { version = "0.10.0-rc.5", features = ["chacha", "sys_rng"] }
|
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"
|
bitcode = "0.6.9"
|
||||||
dashmap = "6.1.0"
|
dashmap = "6.1.0"
|
||||||
fjall = "3.0.1"
|
fjall = "3.0.1"
|
||||||
time = { version = "0.3.47", features = ["local-offset"] }
|
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]]
|
[[bin]]
|
||||||
name = "openworm-client"
|
name = "openworm-client"
|
||||||
@@ -37,8 +40,8 @@ path = "src/bin/client/main.rs"
|
|||||||
name = "openworm-server"
|
name = "openworm-server"
|
||||||
path = "src/bin/server/main.rs"
|
path = "src/bin/server/main.rs"
|
||||||
|
|
||||||
[profile.dev]
|
# [profile.dev]
|
||||||
codegen-backend = "cranelift"
|
# codegen-backend = "cranelift"
|
||||||
|
|
||||||
[profile.dev.package."*"]
|
# [profile.dev.package."*"]
|
||||||
opt-level = 1
|
# 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()
|
.stack()
|
||||||
.set_root(rsc, &mut ui_state);
|
.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 {
|
Self {
|
||||||
ui_state,
|
ui_state,
|
||||||
data: ClientData::load(),
|
data,
|
||||||
state: Default::default(),
|
state: Default::default(),
|
||||||
main_ui,
|
main_ui,
|
||||||
notif,
|
notif,
|
||||||
@@ -90,7 +91,6 @@ impl DefaultAppState for Client {
|
|||||||
|
|
||||||
fn exit(&mut self, _rsc: &mut DefaultRsc<Self>, _render: &mut UiRenderState) {
|
fn exit(&mut self, _rsc: &mut DefaultRsc<Self>, _render: &mut UiRenderState) {
|
||||||
self.state.exit();
|
self.state.exit();
|
||||||
self.data.save();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn window_event(
|
fn window_event(
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use crate::ClientEvent;
|
use crate::ClientEvent;
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use openworm::net::{
|
use openworm::net::{
|
||||||
ClientMsg, ClientRequestMsg, CreateAccount, CreateAccountResp, RecvHandler, RequestId,
|
ClientMsg, ClientRequestMsg, CreateAccount, CreateAccountResp, Login, LoginResp, RecvHandler,
|
||||||
SERVER_NAME, ServerMsg, ServerRespMsg, SkipServerVerification, recv_uni, send_uni,
|
RequestId, SERVER_NAME, ServerMsg, ServerRespMsg, SkipServerVerification, recv_uni, send_uni,
|
||||||
};
|
};
|
||||||
use quinn::{
|
use quinn::{
|
||||||
ClientConfig, Connection, Endpoint, IdleTimeout, TransportConfig,
|
ClientConfig, Connection, Endpoint, IdleTimeout, TransportConfig,
|
||||||
@@ -10,6 +10,7 @@ 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,
|
||||||
};
|
};
|
||||||
@@ -83,7 +84,7 @@ impl RequestMsg for CreateAccount {
|
|||||||
type Result = CreateAccountResp;
|
type Result = CreateAccountResp;
|
||||||
|
|
||||||
fn result(msg: ServerMsg) -> Option<Self::Result> {
|
fn result(msg: ServerMsg) -> Option<Self::Result> {
|
||||||
if let ServerMsg::CreateAccount(res) = msg {
|
if let ServerMsg::CreateAccountResp(res) = msg {
|
||||||
Some(res)
|
Some(res)
|
||||||
} else {
|
} else {
|
||||||
None
|
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();
|
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()
|
let client_crypto = quinn::rustls::ClientConfig::builder()
|
||||||
.with_root_certificates(roots)
|
.with_root_certificates(roots)
|
||||||
.with_no_client_auth();
|
.with_no_client_auth();
|
||||||
let client_config = ClientConfig::new(Arc::new(QuicClientConfig::try_from(client_crypto)?));
|
let client_config = ClientConfig::new(Arc::new(
|
||||||
let mut endpoint = quinn::Endpoint::client(SocketAddr::from_str("[::]:0").unwrap())?;
|
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.set_default_client_config(client_config);
|
||||||
endpoint
|
let conn = endpoint
|
||||||
.connect(addr, SERVER_NAME)?
|
.connect(addr, SERVER_NAME)
|
||||||
|
.map_err(|e| e.to_string())?
|
||||||
.await
|
.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)> {
|
async fn connection_no_cert(addr: SocketAddr) -> NetResult<(Endpoint, Connection)> {
|
||||||
@@ -148,7 +171,7 @@ impl NetHandle {
|
|||||||
.map_err(|e| e.to_string())?
|
.map_err(|e| e.to_string())?
|
||||||
.next()
|
.next()
|
||||||
.ok_or("no addresses found".to_string())?;
|
.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 conn_ = conn.clone();
|
||||||
|
|
||||||
let mut req_id = RequestId::first();
|
let mut req_id = RequestId::first();
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ use super::*;
|
|||||||
|
|
||||||
pub const MODAL_BG: UiColor = UiColor::BLACK.brighter(0.05);
|
pub const MODAL_BG: UiColor = UiColor::BLACK.brighter(0.05);
|
||||||
pub const GREEN: UiColor = UiColor::rgb(0, 150, 0);
|
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::*;
|
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 mut accounts = Span::empty(Dir::DOWN);
|
||||||
|
|
||||||
|
let accts = data.accounts();
|
||||||
|
if accts.is_empty() {
|
||||||
accounts.push(
|
accounts.push(
|
||||||
wtext("no accounts")
|
wtext("no accounts")
|
||||||
.size(20)
|
.size(20)
|
||||||
@@ -15,6 +20,82 @@ pub fn start(rsc: &mut Rsc) -> WeakWidget {
|
|||||||
.height(60)
|
.height(60)
|
||||||
.add_strong(rsc),
|
.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 connect = Button::submit("connect", rsc);
|
||||||
let create = Button::submit("create", 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);
|
let create = Button::submit("create", rsc);
|
||||||
rsc.events.register(create, Submit, move |ctx, rsc| {
|
rsc.events.register(create, Submit, move |ctx, rsc| {
|
||||||
let url = rsc[url].content();
|
let url = rsc[url].content().trim().to_string();
|
||||||
let token = rsc[token].content();
|
let token = rsc[token].content().trim().to_string();
|
||||||
let cert = rsc[cert].content();
|
let cert_hex = rsc[cert].content().trim().to_string();
|
||||||
let Ok(cert) = decode_hex(&cert) else {
|
let Some(cert) = decode_hex(&cert_hex) else {
|
||||||
rsc[ctx.state.notif].inner = Some(werror("Invalid certificate hex", rsc));
|
rsc[ctx.state.notif].inner = Some(werror("Invalid certificate hex", rsc));
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let username = rsc[username].content();
|
let username = rsc[username].content();
|
||||||
let password = rsc[password].content();
|
let password = rsc[password].content();
|
||||||
let login_key = ctx.state.data.login_key(&username);
|
|
||||||
create.disable(rsc);
|
create.disable(rsc);
|
||||||
rsc.spawn_task(async move |mut ctx| {
|
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| {
|
ctx.update(move |ctx, rsc| {
|
||||||
rsc[ctx.notif].inner = Some(werror(reason, rsc));
|
rsc[ctx.notif].inner = Some(werror(&reason, rsc));
|
||||||
create.enable(rsc);
|
create.enable(rsc);
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
let Ok(net) = NetHandle::connect(
|
keyring::use_native_store(true).unwrap();
|
||||||
|
let net = match NetHandle::connect(
|
||||||
async |msg| {
|
async |msg| {
|
||||||
println!("msg recv :joy:");
|
println!("msg recv :joy:");
|
||||||
},
|
},
|
||||||
ConnectInfo { url, cert },
|
ConnectInfo {
|
||||||
|
url: url.clone(),
|
||||||
|
cert,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
else {
|
{
|
||||||
return fail("failed to connect");
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
return fail(&e);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let Ok(resp) = net
|
let Ok(resp) = net
|
||||||
.request(CreateAccount {
|
.request(CreateAccount {
|
||||||
username,
|
username: username.clone(),
|
||||||
password,
|
password: password.clone(),
|
||||||
token,
|
token,
|
||||||
login_key,
|
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
else {
|
else {
|
||||||
@@ -96,7 +184,14 @@ pub fn create_account(rsc: &mut Rsc) -> WeakWidget {
|
|||||||
return fail("invalid account token");
|
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)
|
.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())
|
(0..s.len())
|
||||||
.step_by(2)
|
.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()
|
.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 {
|
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 rect = rect(color).radius(15).add(rsc);
|
||||||
let enabled = rsc.create_state(rect, true);
|
let enabled = rsc.create_state(rect, true);
|
||||||
let root = rect
|
let root = rect
|
||||||
@@ -58,7 +58,7 @@ impl Button {
|
|||||||
rsc[ctx.widget].color = color;
|
rsc[ctx.widget].color = color;
|
||||||
})
|
})
|
||||||
.height(60)
|
.height(60)
|
||||||
.foreground(wtext(text).size(25).text_align(Align::CENTER))
|
.foreground(fg)
|
||||||
.add(rsc);
|
.add(rsc);
|
||||||
rect.on(CursorSense::click(), move |ctx, rsc: &mut Rsc| {
|
rect.on(CursorSense::click(), move |ctx, rsc: &mut Rsc| {
|
||||||
if !rsc[enabled] {
|
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 {
|
pub fn submit(text: &str, rsc: &mut Rsc) -> Self {
|
||||||
Self::new(text, color::GREEN, rsc)
|
Self::new(text, color::GREEN, rsc)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ use net::{ClientSender, ConAccepter, listen};
|
|||||||
use openworm::{
|
use openworm::{
|
||||||
net::{
|
net::{
|
||||||
ClientMsg, ClientRequestMsg, CreateAccount, CreateAccountResp, DisconnectReason, LoadMsg,
|
ClientMsg, ClientRequestMsg, CreateAccount, CreateAccountResp, DisconnectReason, LoadMsg,
|
||||||
RecvHandler, ServerError, ServerMsg, install_crypto_provider,
|
Login, LoginResp, RecvHandler, ServerError, ServerMsg, install_crypto_provider,
|
||||||
},
|
},
|
||||||
rsc::DataDir,
|
rsc::DataDir,
|
||||||
};
|
};
|
||||||
use rand::distr::{Alphanumeric, SampleString};
|
use rand::distr::{Alphanumeric, SampleString};
|
||||||
use scrypt::{
|
use scrypt::{
|
||||||
Scrypt,
|
Scrypt,
|
||||||
password_hash::{PasswordHasher, SaltString, rand_core::OsRng},
|
password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString, rand_core::OsRng},
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
@@ -41,9 +41,8 @@ fn main() {
|
|||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
pub async fn run_server(port: u16) {
|
pub async fn run_server(port: u16) {
|
||||||
let dir = DataDir::default();
|
let dir = DataDir::new(Some("server"));
|
||||||
let path = dir.get().join("server");
|
let db = Db::open(dir.path.join("db"));
|
||||||
let db = Db::open(path.join("db"));
|
|
||||||
let handler = ServerListener {
|
let handler = ServerListener {
|
||||||
senders: Default::default(),
|
senders: Default::default(),
|
||||||
count: 0.into(),
|
count: 0.into(),
|
||||||
@@ -53,7 +52,7 @@ pub async fn run_server(port: u16) {
|
|||||||
let token = account_token(&db, ServerPerms::ACCOUNT_TOKENS);
|
let token = account_token(&db, ServerPerms::ACCOUNT_TOKENS);
|
||||||
println!("no users found, token for admin: {token}");
|
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;
|
let _ = ctrl_c().await;
|
||||||
println!("stopping server");
|
println!("stopping server");
|
||||||
println!("closing connections...");
|
println!("closing connections...");
|
||||||
@@ -171,7 +170,6 @@ impl RecvHandler<ClientRequestMsg> for ClientHandler {
|
|||||||
token,
|
token,
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
login_key,
|
|
||||||
} = &info;
|
} = &info;
|
||||||
let salt = SaltString::generate(&mut OsRng);
|
let salt = SaltString::generate(&mut OsRng);
|
||||||
let params = scrypt::Params::new(11, 8, 1, 32).unwrap();
|
let params = scrypt::Params::new(11, 8, 1, 32).unwrap();
|
||||||
@@ -214,24 +212,26 @@ impl RecvHandler<ClientRequestMsg> for ClientHandler {
|
|||||||
println!("account created: \"{username}\"");
|
println!("account created: \"{username}\"");
|
||||||
*self.state.write().await = ClientState::Authed(id);
|
*self.state.write().await = ClientState::Authed(id);
|
||||||
let _ = replier.send(CreateAccountResp::Ok { id }).await;
|
let _ = replier.send(CreateAccountResp::Ok { id }).await;
|
||||||
} // ClientMsgType::Login { username, password } => {
|
}
|
||||||
// let Some(id) = self.db.usernames.get(&username) else {
|
ClientMsg::Login(info) => {
|
||||||
// let _ = self.send.send(ServerError::UnknownUsername).await;
|
let Login { username, password } = &info;
|
||||||
// return;
|
let Some(id) = self.db.usernames.get(username) else {
|
||||||
// };
|
let _ = replier.send(LoginResp::UnknownUsername).await;
|
||||||
// let Some(user) = self.db.users.get(&id) else {
|
return;
|
||||||
// panic!("invalid state! (should be a user)");
|
};
|
||||||
// };
|
let Some(user) = self.db.users.get(&id) else {
|
||||||
// let hash = PasswordHash::new(&user.password_hash).unwrap();
|
panic!("invalid state! (should be a user)");
|
||||||
// if Scrypt.verify_password(password.as_bytes(), &hash).is_err() {
|
};
|
||||||
// println!("invalid password: \"{username}\"");
|
let hash = PasswordHash::new(&user.password_hash).unwrap();
|
||||||
// let _ = self.send.send(ServerError::InvalidPassword).await;
|
if Scrypt.verify_password(password.as_bytes(), &hash).is_err() {
|
||||||
// return;
|
println!("invalid password: \"{username}\"");
|
||||||
// }
|
let _ = replier.send(LoginResp::InvalidPassword).await;
|
||||||
// println!("login: \"{username}\"");
|
return;
|
||||||
// *self.state.write().await = ClientState::Authed(id);
|
}
|
||||||
// let _ = self.send.send(ServerMsgType::Login { username }).await;
|
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: ");
|
print!("cert hex: ");
|
||||||
for x in cert.iter() {
|
for x in cert.iter() {
|
||||||
print!("{:x}", x);
|
print!("{:02x}", x);
|
||||||
}
|
}
|
||||||
println!();
|
println!();
|
||||||
let server_config = ServerConfig::with_single_cert(vec![cert], key).unwrap();
|
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::CreateAccount(v) => Self::CreateAccountV0(v),
|
||||||
ClientMsg::RequestMsgs => Self::RequestMsgsV0,
|
ClientMsg::RequestMsgs => Self::RequestMsgsV0,
|
||||||
ClientMsg::SendMsg(v) => Self::SendMsgV0(v),
|
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::CreateAccountV0(v) => Self::CreateAccount(v),
|
||||||
ClientMsgInst::RequestMsgsV0 => Self::RequestMsgs,
|
ClientMsgInst::RequestMsgsV0 => Self::RequestMsgs,
|
||||||
ClientMsgInst::SendMsgV0(v) => Self::SendMsg(v),
|
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 {
|
impl From<ServerMsg> for ServerMsgInst {
|
||||||
fn from(value: ServerMsg) -> Self {
|
fn from(value: ServerMsg) -> Self {
|
||||||
match value {
|
match value {
|
||||||
ServerMsg::CreateAccount(v) => Self::CreateAccountV0(v),
|
ServerMsg::CreateAccountResp(v) => Self::CreateAccountRespV0(v),
|
||||||
ServerMsg::LoadMsg(v) => Self::LoadMsgV0(v),
|
ServerMsg::LoadMsg(v) => Self::LoadMsgV0(v),
|
||||||
ServerMsg::LoadMsgs(v) => Self::LoadMsgsV0(v),
|
ServerMsg::LoadMsgs(v) => Self::LoadMsgsV0(v),
|
||||||
ServerMsg::ServerError(v) => Self::ServerErrorV0(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 {
|
impl From<ServerMsgInst> for ServerMsg {
|
||||||
fn from(value: ServerMsgInst) -> Self {
|
fn from(value: ServerMsgInst) -> Self {
|
||||||
match value {
|
match value {
|
||||||
ServerMsgInst::CreateAccountV0(v) => Self::CreateAccount(v),
|
ServerMsgInst::CreateAccountRespV0(v) => Self::CreateAccountResp(v),
|
||||||
ServerMsgInst::LoadMsgV0(v) => Self::LoadMsg(v),
|
ServerMsgInst::LoadMsgV0(v) => Self::LoadMsg(v),
|
||||||
ServerMsgInst::LoadMsgsV0(v) => Self::LoadMsgs(v),
|
ServerMsgInst::LoadMsgsV0(v) => Self::LoadMsgs(v),
|
||||||
ServerMsgInst::ServerErrorV0(v) => Self::ServerError(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)]
|
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
|
||||||
pub enum ClientMsgInst {
|
pub enum ClientMsgInst {
|
||||||
CreateAccountV0(CreateAccountV0) = 0,
|
CreateAccountV0(CreateAccountV0) = 0,
|
||||||
RequestMsgsV0 = 1,
|
LoginV0(LoginV0) = 1,
|
||||||
SendMsgV0(SendMsgV0) = 2,
|
RequestMsgsV0 = 2,
|
||||||
|
SendMsgV0(SendMsgV0) = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
|
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
|
||||||
pub enum ServerMsgInst {
|
pub enum ServerMsgInst {
|
||||||
CreateAccountV0(CreateAccountRespV0) = 0,
|
CreateAccountRespV0(CreateAccountRespV0) = 0,
|
||||||
LoadMsgV0(LoadMsgV0) = 1,
|
LoginRespV0(LoginRespV0) = 1,
|
||||||
LoadMsgsV0(Vec<LoadMsgV0>) = 2,
|
LoadMsgV0(LoadMsgV0) = 2,
|
||||||
ServerErrorV0(ServerErrorV0) = 3,
|
LoadMsgsV0(Vec<LoadMsgV0>) = 3,
|
||||||
|
ServerErrorV0(ServerErrorV0) = 4,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type UserIdV0 = u64;
|
pub type UserIdV0 = u64;
|
||||||
@@ -24,7 +26,12 @@ pub struct CreateAccountV0 {
|
|||||||
pub username: String,
|
pub username: String,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
pub token: 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)]
|
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
|
||||||
@@ -34,6 +41,13 @@ pub enum CreateAccountRespV0 {
|
|||||||
InvalidToken,
|
InvalidToken,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
|
||||||
|
pub enum LoginRespV0 {
|
||||||
|
Ok { id: UserIdV0 },
|
||||||
|
UnknownUsername,
|
||||||
|
InvalidPassword,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
|
#[derive(Debug, bitcode::Encode, bitcode::Decode)]
|
||||||
pub struct LoginKeyV0(Vec<u8>);
|
pub struct LoginKeyV0(Vec<u8>);
|
||||||
impl LoginKeyV0 {
|
impl LoginKeyV0 {
|
||||||
@@ -47,6 +61,10 @@ impl LoginKeyV0 {
|
|||||||
.expect("failed to generate random key");
|
.expect("failed to generate random key");
|
||||||
Self(key.to_vec())
|
Self(key.to_vec())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn bytes(&self) -> &[u8] {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
impl From<Vec<u8>> for LoginKeyV0 {
|
impl From<Vec<u8>> for LoginKeyV0 {
|
||||||
fn from(value: Vec<u8>) -> Self {
|
fn from(value: Vec<u8>) -> Self {
|
||||||
|
|||||||
@@ -3,13 +3,15 @@ use super::data::*;
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ClientMsg {
|
pub enum ClientMsg {
|
||||||
CreateAccount(CreateAccount),
|
CreateAccount(CreateAccount),
|
||||||
|
Login(Login),
|
||||||
RequestMsgs,
|
RequestMsgs,
|
||||||
SendMsg(SendMsg),
|
SendMsg(SendMsg),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ServerMsg {
|
pub enum ServerMsg {
|
||||||
CreateAccount(CreateAccountResp),
|
CreateAccountResp(CreateAccountResp),
|
||||||
|
LoginResp(LoginResp),
|
||||||
LoadMsg(LoadMsg),
|
LoadMsg(LoadMsg),
|
||||||
LoadMsgs(Vec<LoadMsg>),
|
LoadMsgs(Vec<LoadMsg>),
|
||||||
ServerError(ServerError),
|
ServerError(ServerError),
|
||||||
@@ -21,6 +23,8 @@ pub type LoadMsg = LoadMsgV0;
|
|||||||
pub type ServerError = ServerErrorV0;
|
pub type ServerError = ServerErrorV0;
|
||||||
pub type CreateAccount = CreateAccountV0;
|
pub type CreateAccount = CreateAccountV0;
|
||||||
pub type CreateAccountResp = CreateAccountRespV0;
|
pub type CreateAccountResp = CreateAccountRespV0;
|
||||||
|
pub type Login = LoginV0;
|
||||||
|
pub type LoginResp = LoginRespV0;
|
||||||
pub type UserId = UserIdV0;
|
pub type UserId = UserIdV0;
|
||||||
|
|
||||||
impl From<CreateAccount> for ClientMsg {
|
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 {
|
impl From<ServerError> for ServerMsg {
|
||||||
fn from(value: ServerError) -> Self {
|
fn from(value: ServerError) -> Self {
|
||||||
Self::ServerError(value)
|
Self::ServerError(value)
|
||||||
@@ -43,6 +53,12 @@ impl From<LoadMsg> for ServerMsg {
|
|||||||
|
|
||||||
impl From<CreateAccountResp> for ServerMsg {
|
impl From<CreateAccountResp> for ServerMsg {
|
||||||
fn from(value: CreateAccountResp) -> Self {
|
fn from(value: CreateAccountResp) -> Self {
|
||||||
Self::CreateAccount(value)
|
Self::CreateAccountResp(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<LoginResp> for ServerMsg {
|
||||||
|
fn from(value: LoginResp) -> Self {
|
||||||
|
Self::LoginResp(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
109
src/rsc.rs
109
src/rsc.rs
@@ -1,50 +1,103 @@
|
|||||||
use std::{
|
use std::{
|
||||||
fs::{self, File},
|
fs::{self, File},
|
||||||
io::Write,
|
io::Write,
|
||||||
path::Path,
|
path::PathBuf,
|
||||||
};
|
};
|
||||||
|
|
||||||
use directories_next::ProjectDirs;
|
use directories_next::ProjectDirs;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct DataDir {
|
pub struct DataDir {
|
||||||
dirs: ProjectDirs,
|
pub path: PathBuf,
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DataDir {
|
impl DataDir {
|
||||||
pub fn get(&self) -> &Path {
|
pub fn new(dir: Option<&str>) -> Self {
|
||||||
self.dirs.data_local_dir()
|
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 fn load<T: DataRsc>(&self) -> T {
|
pub trait DataRsc: serde::Serialize + Default {
|
||||||
let path = self.get().join(T::path());
|
fn name() -> &'static str;
|
||||||
match fs::read(path) {
|
fn path() -> String {
|
||||||
Ok(bytes) => match bitcode::decode(&bytes) {
|
Self::name().to_string() + ".ron"
|
||||||
Ok(data) => data,
|
}
|
||||||
Err(_) => todo!(),
|
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(),
|
||||||
},
|
},
|
||||||
Err(_) => T::default(),
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save<T: DataRsc>(&self, data: &T) {
|
impl<T: DataRsc> std::ops::Deref for DataGuard<T> {
|
||||||
let dir = self.get();
|
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();
|
fs::create_dir_all(dir).unwrap();
|
||||||
let mut file = File::create(dir.join(T::path())).unwrap();
|
let mut file = File::create(dir.join(T::path())).unwrap();
|
||||||
// TODO: used to use encode_into_std_write from bincode here
|
let ron = ron::to_string(&self.val).unwrap();
|
||||||
let data = bitcode::encode(data);
|
let data = format!("// v{}\n{}\n", T::version(), ron);
|
||||||
file.write_all(&data).unwrap();
|
if let Err(e) = file.write_all(data.as_bytes()) {
|
||||||
|
println!("Failed to write config @ {:?}: {e}", self.path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user