diff --git a/Cargo.lock b/Cargo.lock index f347bcd..f1780d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,18 @@ dependencies = [ "libc", ] +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + [[package]] name = "async-trait" version = "0.1.57" @@ -34,6 +46,29 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "blake3" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08e53fc5a564bb15bfe6fae56bd71522205f1f91893f9c0116edad6496c183f" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "digest", +] + +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array", +] + [[package]] name = "bumpalo" version = "3.11.0" @@ -52,6 +87,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + [[package]] name = "cfg-if" version = "1.0.0" @@ -73,6 +114,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + [[package]] name = "core-foundation-sys" version = "0.8.3" @@ -112,6 +159,27 @@ dependencies = [ "once_cell", ] +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + [[package]] name = "fs2" version = "0.4.3" @@ -131,6 +199,16 @@ dependencies = [ "byteorder", ] +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.7" @@ -164,6 +242,7 @@ dependencies = [ name = "harsh_server" version = "0.1.0" dependencies = [ + "blake3", "chrono", "harsh_common", "rand", @@ -508,6 +587,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + [[package]] name = "syn" version = "1.0.99" @@ -572,12 +657,24 @@ dependencies = [ "syn", ] +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + [[package]] name = "unicode-ident" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" diff --git a/harsh-client/src/commands.rs b/harsh-client/src/commands.rs index 5be95e9..3f49726 100644 --- a/harsh-client/src/commands.rs +++ b/harsh-client/src/commands.rs @@ -87,7 +87,7 @@ pub fn parse(input: &str) -> Option { Some(Command::Request(command)) } -pub const CMDS: &'static [Description] = &[ +pub const CMDS: &[Description] = &[ // all commands Description::new("help", &[], "returns a help message"), Description::new( diff --git a/harsh-client/src/main.rs b/harsh-client/src/main.rs index 73d4442..b88a3cf 100644 --- a/harsh-client/src/main.rs +++ b/harsh-client/src/main.rs @@ -3,13 +3,13 @@ use std::{ process::exit, }; -use harsh_common::ServerRequest; +use harsh_common::ServerEvent; use tokio::{ io::{stdin, AsyncBufReadExt, AsyncWriteExt, BufReader}, net::TcpStream, }; -const ADDRESS: &'static str = "localhost:42069"; +const ADDRESS: &str = "localhost:42000"; #[tokio::main] async fn main() { @@ -21,13 +21,10 @@ async fn main() { let mut reader = BufReader::new(reader); loop { let mut line = String::new(); - match reader.read_line(&mut line).await { - Ok(0) => { - break; - } - _ => (), + if let Ok(0) = reader.read_line(&mut line).await { + break; } - if let Some(parsed) = ServerRequest::try_parse(&line) { + if let Some(parsed) = ServerEvent::try_parse(&line) { println!("[main/info] received '{parsed:?}'"); } } diff --git a/harsh-common/src/client.rs b/harsh-common/src/client.rs index 13540be..62cfb96 100644 --- a/harsh-common/src/client.rs +++ b/harsh-common/src/client.rs @@ -3,6 +3,12 @@ pub struct Ping { pub content: String, } +#[derive(Debug)] +pub struct Authenticate { + pub id: u64, + pub pass: String, +} + #[derive(Debug)] pub struct ChannelList {} @@ -92,6 +98,8 @@ pub struct UserSetPass { #[derive(Debug)] pub enum ClientRequest { Ping(Ping), + Authenticate(Authenticate), + ChannelList(ChannelList), ChannelCreate(ChannelCreate), ChannelDelete(ChannelDelete), @@ -117,6 +125,10 @@ impl ClientRequest { Self::Ping(Ping { content }) } + pub fn new_authenticate(id: u64, pass: String) -> Self { + Self::Authenticate(Authenticate { id, pass }) + } + pub fn new_channel_list() -> Self { Self::ChannelList(ChannelList {}) } @@ -185,51 +197,31 @@ impl ClientRequest { use repr::Command::*; let command: repr::Command = serde_json::from_str(line).ok()?; let mapped = match command { - ping { content } => Self::Ping(Ping { content }), - channel_list {} => Self::ChannelList(ChannelList {}), - channel_create { name } => Self::ChannelCreate(ChannelCreate { name }), - channel_delete { id: channel_id } => { - Self::ChannelDelete(ChannelDelete { id: channel_id }) - } - channel_get_name { id: channel_id } => { - Self::ChannelGetName(ChannelGetName { id: channel_id }) - } - channel_set_name { - id: channel_id, - name, - } => Self::ChannelSetName(ChannelSetName { - id: channel_id, - name, - }), - message_list { channel_id } => Self::MessageList(MessageList { channel_id }), + ping { content } => Self::new_ping(content), + authenticate { id, pass } => Self::new_authenticate(id, pass), + channel_list {} => Self::new_channel_list(), + channel_create { name } => Self::new_channel_create(name), + channel_delete { id } => Self::new_channel_delete(id), + channel_get_name { id } => Self::new_channel_get_name(id), + channel_set_name { id, name } => Self::new_channel_set_name(id, name), + message_list { channel_id } => Self::new_message_list(channel_id), message_create { channel_id, content, - } => Self::MessageCreate(MessageCreate { - channel_id, - content, - }), - message_delete { id, channel_id } => { - Self::MessageDelete(MessageDelete { id, channel_id }) - } - message_get_content { id, channel_id } => { - Self::MessageGetContent(MessageGetContent { id, channel_id }) - } + } => Self::new_message_create(channel_id, content), + message_delete { id, channel_id } => Self::new_message_delete(channel_id, id), + message_get_content { id, channel_id } => Self::new_message_get_content(channel_id, id), message_set_content { id, channel_id, content, - } => Self::MessageSetContent(MessageSetContent { - content, - id, - channel_id, - }), - user_list {} => Self::UserList(UserList {}), - user_create { name, pass } => Self::UserCreate(UserCreate { name, pass }), - user_delete { id } => Self::UserDelete(UserDelete { id }), - user_get_name { id } => Self::UserGetName(UserGetName { id }), - user_set_name { id, name } => Self::UserSetName(UserSetName { id, name }), - user_set_pass { id, pass } => Self::UserSetPass(UserSetPass { id, pass }), + } => Self::new_message_set_content(channel_id, id, content), + user_list {} => Self::new_user_list(), + user_create { name, pass } => Self::new_user_create(name, pass), + user_delete { id } => Self::new_user_delete(id), + user_get_name { id } => Self::new_user_get_name(id), + user_set_name { id, name } => Self::new_user_set_name(id, name), + user_set_pass { id, pass } => Self::new_user_set_pass(id, pass), }; Some(mapped) } @@ -238,6 +230,7 @@ impl ClientRequest { use repr::Command::*; let mapped = match self { Self::Ping(Ping { content }) => ping { content }, + Self::Authenticate(Authenticate { id, pass }) => authenticate { id, pass }, Self::ChannelList(ChannelList {}) => repr::Command::channel_list {}, Self::ChannelCreate(ChannelCreate { name }) => channel_create { name }, Self::ChannelDelete(ChannelDelete { id: channel_id }) => { @@ -298,6 +291,10 @@ mod repr { ping { content: String, }, + authenticate { + id: u64, + pass: String, + }, channel_list {}, channel_create { name: String, diff --git a/harsh-common/src/lib.rs b/harsh-common/src/lib.rs index 38e31f5..2b3f636 100644 --- a/harsh-common/src/lib.rs +++ b/harsh-common/src/lib.rs @@ -10,5 +10,5 @@ mod tests { pub use client::ClientRequest; pub mod client; -pub use server::ServerRequest; +pub use server::ServerEvent; pub mod server; diff --git a/harsh-common/src/server.rs b/harsh-common/src/server.rs index b19900d..c86a994 100644 --- a/harsh-common/src/server.rs +++ b/harsh-common/src/server.rs @@ -94,7 +94,7 @@ pub struct UserSetPass { } #[derive(Debug)] -pub enum ServerRequest { +pub enum ServerEvent { Pong(Pong), ChannelCreate(ChannelCreate), @@ -117,7 +117,7 @@ pub enum ServerRequest { UserSetPass(UserSetPass), } -impl ServerRequest { +impl ServerEvent { pub fn new_pong(content: String) -> Self { Self::Pong(Pong { content }) } diff --git a/harsh-server/Cargo.toml b/harsh-server/Cargo.toml index cd7b892..41073eb 100644 --- a/harsh-server/Cargo.toml +++ b/harsh-server/Cargo.toml @@ -15,3 +15,4 @@ tokio = { version = "1.20", features = ["full"] } harsh_common = { path = "../harsh-common/" } chrono = "0.4" rand = "0.8.5" +blake3 = "1.3.1" diff --git a/harsh-server/src/gateway.rs b/harsh-server/src/gateway.rs index d1c61b0..9748847 100644 --- a/harsh-server/src/gateway.rs +++ b/harsh-server/src/gateway.rs @@ -1,7 +1,10 @@ -use harsh_common::{client, server, ClientRequest, ServerRequest}; +use harsh_common::{client, server, ClientRequest, ServerEvent}; use telecomande::{Processor, Remote}; -use crate::{Addr, Id, SessionCmd, SessionProc, StorageCmd, StorageProc}; +use crate::{ + sessions::SessionExt, storage::Perm, Addr, Id, SecurityCmd, SecurityProc, SessionCmd, + SessionProc, StorageCmd, StorageProc, +}; #[derive(Debug)] pub enum GatewayCmd { @@ -12,193 +15,246 @@ pub enum GatewayCmd { pub struct GatewayProc { sessions: Remote, storage: Remote, + security: Remote, } +use client::*; + impl GatewayProc { - async fn handle_request(&mut self, address: Addr, request: ClientRequest) { - use client as c; - use ClientRequest::*; + async fn handle_request(&mut self, addr: Addr, request: ClientRequest) -> Result<(), String> { + use client::*; + use ClientRequest as CR; + + // auth-free API + let request = match request { + CR::Ping(ping) => return self.on_ping(ping, addr), + CR::Authenticate(authenticate) => { + return self.on_authenticate(authenticate, addr).await + } + _ => request, + }; + + // let user = self.sessions.get_user(addr.clone()).await.ok_or("owno")?; + + // auth API match request { - Ping(c::Ping { content }) => self.on_ping(content, address), + CR::Ping(_) | CR::Authenticate(_) => unreachable!(), - ChannelCreate(c::ChannelCreate { name }) => self.on_channel_create(name).await, - ChannelDelete(c::ChannelDelete { id }) => self.on_channel_delete(id), - ChannelList(c::ChannelList {}) => self.on_channel_list(address).await, - ChannelGetName(c::ChannelGetName { id }) => self.on_channel_get_name(id, address).await, - ChannelSetName(c::ChannelSetName { id, name }) => self.on_channel_set_name(id, name), + CR::ChannelCreate(req) => self.on_channel_create(req).await, + CR::ChannelDelete(req) => todo!(), //self.on_channel_delete(req, user).await, + CR::ChannelList(req) => self.on_channel_list(req, addr).await, + CR::ChannelGetName(req) => self.on_channel_get_name(req, addr).await, + CR::ChannelSetName(req) => self.on_channel_set_name(req), - MessageList(c::MessageList { channel_id }) => { - self.on_message_list(channel_id, address).await - } - MessageCreate(c::MessageCreate { - channel_id, - content, - }) => self.on_message_create(channel_id, content).await, - MessageDelete(c::MessageDelete { channel_id, id }) => { - self.on_message_delete(channel_id, id) - } - MessageGetContent(c::MessageGetContent { channel_id, id }) => { - self.on_message_get_content(channel_id, id, address).await - } + CR::MessageList(req) => self.on_message_list(req, addr).await, + CR::MessageCreate(req) => self.on_message_create(req).await, + CR::MessageDelete(req) => self.on_message_delete(req), + CR::MessageGetContent(req) => self.on_message_get_content(req, addr).await, + CR::MessageSetContent(req) => self.on_message_set_content(req), - MessageSetContent(c::MessageSetContent { - channel_id, - id, - content, - }) => { - self.on_message_set_content(channel_id, id, content); - } + CR::UserList(req) => self.on_user_list(req, addr).await, + CR::UserCreate(req) => self.on_user_create(req).await, + CR::UserDelete(req) => self.on_user_delete(req), + CR::UserGetName(req) => self.on_user_get_name(req, addr).await, + CR::UserSetName(req) => self.on_user_set_name(req), + CR::UserSetPass(req) => self.on_user_set_pass(req, addr), + }; + Ok(()) + } - // TODO: user - UserList(c::UserList {}) => { - let (cmd, rec) = StorageCmd::new_user_list(); - self.storage.send(cmd).unwrap(); - let result = rec.await.unwrap().iter().map(Id::to_u64).collect(); - let request = ServerRequest::new_user_list(result); - let command = SessionCmd::new_send(address, request); - self.sessions.send(command).unwrap(); - } + async fn on_authenticate( + &mut self, + Authenticate { id, pass }: Authenticate, + address: Addr, + ) -> Result<(), String> { + let (cmd, rec) = SecurityCmd::new_authenticate(id.into(), pass); + self.security.send(cmd).unwrap(); + if rec.await.unwrap() { + let command = SessionCmd::new_set_user(address, Some(id.into())); + self.sessions.send(command).unwrap(); + } else { + Err("Invalid password")?; + }; + Ok(()) + } - UserCreate(c::UserCreate { name, pass }) => { - let (cmd, rec) = StorageCmd::new_user_create(name.clone(), pass); - self.storage.send(cmd).unwrap(); - let id = rec.await.unwrap(); - let request = ServerRequest::new_user_create(id.into(), name); - let command = SessionCmd::new_broadcast(request); - self.sessions.send(command).unwrap(); - } - - UserDelete(c::UserDelete { id }) => { - let command = StorageCmd::new_user_delete(id.into()); - self.storage.send(command).unwrap(); - let request = ServerRequest::new_user_delete(id.into()); - let command = SessionCmd::new_broadcast(request); - self.sessions.send(command).unwrap(); - } - - UserGetName(c::UserGetName { id }) => { - let (cmd, rec) = StorageCmd::new_user_get_name(id.into()); - self.storage.send(cmd).unwrap(); - let name = rec.await.unwrap(); - let request = ServerRequest::new_user_get_name(id.into(), name); - let command = SessionCmd::new_send(address, request); - self.sessions.send(command).unwrap(); - } - - UserSetName(c::UserSetName { id, name }) => { - let command = StorageCmd::new_user_set_name(id.into(), name.clone()); - self.storage.send(command).unwrap(); - let request = ServerRequest::new_user_set_name(id.into(), name); - let command = SessionCmd::new_broadcast(request); - self.sessions.send(command).unwrap(); - } - - UserSetPass(c::UserSetPass { id, pass }) => { - let command = StorageCmd::new_user_set_pass(id.into(), pass); - self.storage.send(command).unwrap(); - let request = ServerRequest::new_user_set_pass(id.into()); - let command = SessionCmd::new_send(address, request); - self.sessions.send(command).unwrap(); - } + pub fn new( + sessions: Remote, + storage: Remote, + security: Remote, + ) -> Self { + Self { + sessions, + storage, + security, } } - pub fn new(sessions: Remote, storage: Remote) -> Self { - Self { sessions, storage } - } - - fn on_ping(&mut self, content: String, address: Addr) { + fn on_ping(&mut self, Ping { content }: Ping, address: Addr) -> Result<(), String> { println!("[gateway/PING] '{content:?}'"); - let request = ServerRequest::Pong(server::Pong { content }); + let request = ServerEvent::Pong(server::Pong { content }); let command = SessionCmd::new_send(address, request); self.sessions.send(command).unwrap(); + Ok(()) } - async fn on_channel_create(&mut self, name: String) { + async fn on_channel_create(&mut self, ChannelCreate { name }: ChannelCreate) { let (cmd, rec) = StorageCmd::new_channel_create(name.clone()); self.storage.send(cmd).unwrap(); let id = rec.await.unwrap().to_u64(); - let request = ServerRequest::new_channel_create(id, name); + let request = ServerEvent::new_channel_create(id, name); let command = SessionCmd::new_broadcast(request); self.sessions.send(command).unwrap(); } - fn on_channel_delete(&mut self, id: u64) { + async fn on_channel_delete(&mut self, ChannelDelete { id }: ChannelDelete, user: Id) { + // TODO: verify is OP + let (cmd, req) = SecurityCmd::new_verify(user, Perm::OpChannel(id.into())); + self.security.send(cmd).unwrap(); + req.await.unwrap(); let command = StorageCmd::new_channel_delete(id.into()); self.storage.send(command).unwrap(); - let request = ServerRequest::new_channel_delete(id); + let request = ServerEvent::new_channel_delete(id); let command = SessionCmd::new_broadcast(request); self.sessions.send(command).unwrap(); } - async fn on_channel_list(&mut self, address: Addr) { + async fn on_channel_list(&mut self, _: ChannelList, address: Addr) { let (cmd, rec) = StorageCmd::new_channel_list(); self.storage.send(cmd).unwrap(); let channels = rec.await.unwrap().iter().map(|id| id.to_u64()).collect(); - let request = ServerRequest::new_channel_list(channels); + let request = ServerEvent::new_channel_list(channels); let command = SessionCmd::new_send(address, request); self.sessions.send(command).unwrap(); } - async fn on_channel_get_name(&mut self, id: u64, address: Addr) { + async fn on_channel_get_name(&mut self, ChannelGetName { id }: ChannelGetName, address: Addr) { let (cmd, rec) = StorageCmd::new_channel_get_name(id.into()); self.storage.send(cmd).unwrap(); let name = rec.await.unwrap(); - let request = ServerRequest::new_channel_get_name(id, name); + let request = ServerEvent::new_channel_get_name(id, name); let command = SessionCmd::new_send(address, request); self.sessions.send(command).unwrap(); } - fn on_channel_set_name(&mut self, id: u64, name: String) { + fn on_channel_set_name(&mut self, ChannelSetName { id, name }: ChannelSetName) { let command = StorageCmd::new_channel_set_name(id.into(), name.clone()); self.storage.send(command).unwrap(); - let request = ServerRequest::new_channel_set_name(id, name); + let request = ServerEvent::new_channel_set_name(id, name); let command = SessionCmd::new_broadcast(request); self.sessions.send(command).unwrap(); } - async fn on_message_list(&mut self, channel_id: u64, address: Addr) { + async fn on_message_list(&mut self, MessageList { channel_id }: MessageList, address: Addr) { let (cmd, rec) = StorageCmd::new_message_list(channel_id.into()); self.storage.send(cmd).unwrap(); let messages = rec.await.unwrap().iter().map(Id::to_u64).collect(); - let request = ServerRequest::new_message_list(channel_id, messages); + let request = ServerEvent::new_message_list(channel_id, messages); let command = SessionCmd::new_send(address, request); self.sessions.send(command).unwrap(); } - async fn on_message_create(&mut self, channel_id: u64, content: String) { + async fn on_message_create( + &mut self, + MessageCreate { + channel_id, + content, + }: MessageCreate, + ) { let (cmd, rec) = StorageCmd::new_message_create(channel_id.into(), content.clone()); self.storage.send(cmd).unwrap(); let id = rec.await.unwrap(); - let request = ServerRequest::new_message_create(channel_id, id.to_u64(), content); + let request = ServerEvent::new_message_create(channel_id, id.to_u64(), content); let command = SessionCmd::new_broadcast(request); self.sessions.send(command).unwrap(); } - fn on_message_delete(&mut self, channel_id: u64, id: u64) { + fn on_message_delete(&mut self, MessageDelete { channel_id, id }: MessageDelete) { let command = StorageCmd::new_message_delete(channel_id.into(), id.into()); self.storage.send(command).unwrap(); - let request = ServerRequest::new_message_delete(channel_id, id); + let request = ServerEvent::new_message_delete(channel_id, id); let command = SessionCmd::new_broadcast(request); self.sessions.send(command).unwrap(); } - async fn on_message_get_content(&mut self, channel_id: u64, id: u64, address: Addr) { + async fn on_message_get_content( + &mut self, + MessageGetContent { channel_id, id }: MessageGetContent, + address: Addr, + ) { let (cmd, rec) = StorageCmd::new_message_get_content(channel_id.into(), id.into()); self.storage.send(cmd).unwrap(); - let request = ServerRequest::new_message_get_content(channel_id, id, rec.await.unwrap()); + let request = ServerEvent::new_message_get_content(channel_id, id, rec.await.unwrap()); let command = SessionCmd::new_send(address, request); self.sessions.send(command).unwrap(); } - fn on_message_set_content(&mut self, channel_id: u64, id: u64, content: String) { + fn on_message_set_content( + &mut self, + MessageSetContent { + channel_id, + id, + content, + }: MessageSetContent, + ) { let command = StorageCmd::new_message_set_content(channel_id.into(), id.into(), content.clone()); self.storage.send(command).unwrap(); - let request = ServerRequest::new_message_set_content(channel_id, id, content); + let request = ServerEvent::new_message_set_content(channel_id, id, content); let command = SessionCmd::new_broadcast(request); self.sessions.send(command).unwrap(); } + + fn on_user_set_pass(&mut self, UserSetPass { id, pass }: UserSetPass, address: Addr) { + let command = StorageCmd::new_user_set_pass(id.into(), pass); + self.storage.send(command).unwrap(); + let request = ServerEvent::new_user_set_pass(id); + let command = SessionCmd::new_send(address, request); + self.sessions.send(command).unwrap(); + } + + fn on_user_set_name(&mut self, UserSetName { id, name }: UserSetName) { + let command = StorageCmd::new_user_set_name(id.into(), name.clone()); + self.storage.send(command).unwrap(); + let request = ServerEvent::new_user_set_name(id, name); + let command = SessionCmd::new_broadcast(request); + self.sessions.send(command).unwrap(); + } + + async fn on_user_get_name(&mut self, UserGetName { id }: UserGetName, address: Addr) { + let (cmd, rec) = StorageCmd::new_user_get_name(id.into()); + self.storage.send(cmd).unwrap(); + let name = rec.await.unwrap(); + let request = ServerEvent::new_user_get_name(id, name); + let command = SessionCmd::new_send(address, request); + self.sessions.send(command).unwrap(); + } + + fn on_user_delete(&mut self, UserDelete { id }: UserDelete) { + let command = StorageCmd::new_user_delete(id.into()); + self.storage.send(command).unwrap(); + let request = ServerEvent::new_user_delete(id); + let command = SessionCmd::new_broadcast(request); + self.sessions.send(command).unwrap(); + } + + async fn on_user_create(&mut self, UserCreate { name, pass }: UserCreate) { + let (cmd, rec) = StorageCmd::new_user_create(name.clone(), pass); + self.storage.send(cmd).unwrap(); + let id = rec.await.unwrap(); + let request = ServerEvent::new_user_create(id.into(), name); + let command = SessionCmd::new_broadcast(request); + self.sessions.send(command).unwrap(); + } + + async fn on_user_list(&mut self, _: UserList, address: Addr) { + let (cmd, rec) = StorageCmd::new_user_list(); + self.storage.send(cmd).unwrap(); + let result = rec.await.unwrap().iter().map(Id::to_u64).collect(); + let request = ServerEvent::new_user_list(result); + let command = SessionCmd::new_send(address, request); + self.sessions.send(command).unwrap(); + } } #[telecomande::async_trait] @@ -210,9 +266,11 @@ impl Processor for GatewayProc { GatewayCmd::Request(address, request) => { if let Some(request) = ClientRequest::try_parse(&request) { println!("[session/info] received command '{request:?}'"); - self.handle_request(address, request).await; + if let Err(reason) = self.handle_request(address, request).await { + eprintln!("[gateway/warn] exception '{reason}'"); + } } else { - println!("[session/warn] failed to parse command"); + println!("[session/info] failed to parse command"); } } GatewayCmd::ClosedConnection(address) => self diff --git a/harsh-server/src/main.rs b/harsh-server/src/main.rs index bad085c..552df10 100644 --- a/harsh-server/src/main.rs +++ b/harsh-server/src/main.rs @@ -1,23 +1,31 @@ use telecomande::{Executor, SimpleExecutor}; use tokio::net::TcpListener; -const ADDRESS: &'static str = "localhost:42069"; -const DB_PATH: &'static str = "./db.test"; +const ADDRESS: &str = "localhost:42000"; +const DB_PATH: &str = "./db.test"; #[tokio::main] async fn main() { println!("[main/info] starting server ..."); + let sessions = SimpleExecutor::new(SessionProc::default()).spawn(); println!("[main/info] spawned sessions"); + let storage = SimpleExecutor::new(StorageProc::new(DB_PATH)).spawn(); println!("[main/info] spawned storage"); - let gateway = - SimpleExecutor::new(GatewayProc::new(sessions.remote(), storage.remote())).spawn(); + + let security = SimpleExecutor::new(SecurityProc::new(storage.remote())).spawn(); + + let gateway = SimpleExecutor::new(GatewayProc::new( + sessions.remote(), + storage.remote(), + security.remote(), + )) + .spawn(); println!("[main/info] spawned gateway"); let listener = TcpListener::bind(ADDRESS).await.unwrap(); println!("[main/info] listening on '{ADDRESS}' ..."); - let client_handler = sessions.remote(); loop { let (stream, address) = listener.accept().await.unwrap(); @@ -42,3 +50,6 @@ pub use sessions::{SessionCmd, SessionProc}; mod storage; pub use storage::{StorageCmd, StorageProc}; + +mod security; +pub use security::{SecurityCmd, SecurityProc}; diff --git a/harsh-server/src/security.rs b/harsh-server/src/security.rs new file mode 100644 index 0000000..1fb766e --- /dev/null +++ b/harsh-server/src/security.rs @@ -0,0 +1,99 @@ +use telecomande::{Processor, Remote}; +use tokio::sync::oneshot::{self, Receiver, Sender}; + +use crate::{storage::Perm, Id, StorageCmd, StorageProc}; + +#[derive(Debug)] +pub enum SecurityCmd { + Verify(Id, Perm, Sender), + Authenticate(Id, String, Sender), + StorePass(Id, String), +} + +impl SecurityCmd { + pub fn new_verify(user_id: Id, permission: Perm) -> (Self, Receiver) { + let (sender, receiver) = oneshot::channel(); + let command = Self::Verify(user_id, permission, sender); + (command, receiver) + } + + pub fn new_authenticate(user_id: Id, pass: String) -> (Self, Receiver) { + let (sender, receiver) = oneshot::channel(); + let command = Self::Authenticate(user_id, pass, sender); + (command, receiver) + } + + pub fn new_store_pass(user_id: Id, pass: String) -> Self { + Self::StorePass(user_id, pass) + } +} + +pub struct SecurityProc { + storage: Remote, +} + +impl SecurityProc { + pub fn new(storage: Remote) -> Self { + Self { storage } + } + + async fn handle_command(&mut self, command: SecurityCmd) { + match command { + SecurityCmd::Verify(user, perm, sender) => { + let (cmd, req) = StorageCmd::new_perm_server_get_op(); + self.storage.send(cmd).unwrap(); + let serv_ops = req.await.unwrap(); + let is_serv_op = serv_ops.into_iter().any(|i| i == user); + let result = match (is_serv_op, perm) { + (true, _) => true, + (false, Perm::OpChannel(chan_id)) => { + let (cmd, req) = StorageCmd::new_perm_channel_get_op(chan_id); + self.storage.send(cmd).unwrap(); + let channel_ops = req.await.unwrap(); + channel_ops.into_iter().any(|i| i == user) + } + _ => false, + }; + sender.send(result).unwrap(); + } + SecurityCmd::Authenticate(user, pass, sender) => { + let (cmd, rec) = StorageCmd::new_user_get_pass(user); + self.storage.send(cmd).unwrap(); + let stored = rec.await.unwrap(); + let result = stored.map(|stored| stored == hash(pass)).unwrap_or(false); + sender.send(result).unwrap(); + } + SecurityCmd::StorePass(user, pass) => { + let pass = hash(pass); + let command = StorageCmd::new_user_set_pass(user, pass); + self.storage.send(command).unwrap(); + } + } + } +} + +const SALT: &str = ":)"; + +fn hash(input: String) -> String { + let hash = blake3::hash((input + SALT).as_bytes()); + format!("{hash}") +} + +#[test] +fn test_hash() { + assert_eq!( + &hash("arbre".into()), + "c2d3a87dcb76c21a8a935b8e988745f31663c3650a0d3732430eaa323f12ee0f" + ); +} + +#[telecomande::async_trait] +impl Processor for SecurityProc { + type Command = SecurityCmd; + type Error = (); + + async fn handle(&mut self, command: Self::Command) -> Result<(), Self::Error> { + self.handle_command(command).await; + Ok(()) + } +} diff --git a/harsh-server/src/sessions.rs b/harsh-server/src/sessions.rs index 9b9158a..4d94655 100644 --- a/harsh-server/src/sessions.rs +++ b/harsh-server/src/sessions.rs @@ -1,6 +1,6 @@ use std::{collections::HashMap, net::SocketAddr}; -use harsh_common::ServerRequest; +use harsh_common::ServerEvent; use telecomande::{Processor, Remote}; use tokio::{ io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, @@ -8,33 +8,58 @@ use tokio::{ tcp::{OwnedReadHalf, OwnedWriteHalf}, TcpStream, }, + sync::oneshot::{self, Receiver, Sender}, task::JoinHandle, }; -use crate::{gateway, Addr}; +use crate::{gateway, Addr, Id}; #[derive(Debug)] pub enum SessionCmd { AddSession(TcpStream, SocketAddr, Remote), RemoveSession(Addr), Send(Addr, String), Broadcast(String), + GetUser(Addr, Sender>), + SetUser(Addr, Option), } impl SessionCmd { - pub fn new_send(address: Addr, request: ServerRequest) -> Self { + pub fn new_add_session( + stream: TcpStream, + address: SocketAddr, + gateway: Remote, + ) -> Self { + Self::AddSession(stream, address, gateway) + } + + pub fn new_remove_session(address: Addr) -> Self { + Self::RemoveSession(address) + } + + pub fn new_send(address: Addr, request: ServerEvent) -> Self { let content = request.serialize(); Self::Send(address, content) } - pub fn new_broadcast(request: ServerRequest) -> Self { + pub fn new_broadcast(request: ServerEvent) -> Self { let content = request.serialize(); Self::Broadcast(content) } + + pub fn new_get_user(address: Addr) -> (Self, Receiver>) { + let (sender, receiver) = oneshot::channel(); + let command = Self::GetUser(address, sender); + (command, receiver) + } + + pub fn new_set_user(address: Addr, user: Option) -> Self { + Self::SetUser(address, user) + } } #[derive(Debug, Default)] pub struct SessionProc { - clients: HashMap)>, + clients: HashMap, } impl SessionProc { @@ -46,7 +71,7 @@ impl SessionProc { ) { let (reader, writer) = stream.into_split(); let handle = tokio::spawn(session(address.clone(), reader, remote)); - self.clients.insert(address, (writer, handle)); + self.clients.insert(address, Client::new(writer, handle)); } } @@ -65,24 +90,29 @@ impl Processor for SessionProc { } SessionCmd::RemoveSession(address) => { println!("[sessions/info] closed connection from '{address:?}'"); - if let Some((_writer, handle)) = self.clients.remove(&address) { - handle.await.unwrap(); + if let Some(client) = self.clients.remove(&address) { + client.unwrap().await.unwrap(); } } SessionCmd::Send(address, content) => { - if let Some((client, _)) = self.clients.get_mut(&address) { + if let Some(client) = self.clients.get_mut(&address) { println!("[session/info] sending '{content}' to '{address:?}'"); - client.write_all(content.as_bytes()).await.unwrap(); - client.write_all(b"\n").await.unwrap(); - } else { - eprintln!("failed to find session with address '{address:?}'") + client.send(&content).await; } } SessionCmd::Broadcast(content) => { - for (client, _) in self.clients.values_mut() { - println!("[session/info] broadcasting '{content}'"); - client.write_all(content.as_bytes()).await.unwrap(); - client.write_all(b"\n").await.unwrap(); + println!("[session/info] broadcasting '{content}'"); + for client in self.clients.values_mut() { + client.send(&content).await; + } + } + SessionCmd::GetUser(address, sender) => { + let user = self.clients.get_mut(&address).and_then(|c| c.get_user()); + sender.send(user).unwrap(); + } + SessionCmd::SetUser(address, user) => { + if let Some(client) = self.clients.get_mut(&address) { + client.set_user(user); } } }; @@ -90,6 +120,45 @@ impl Processor for SessionProc { } } +#[derive(Debug)] +pub struct Client { + writer: OwnedWriteHalf, + handle: JoinHandle<()>, + user: Option, +} + +impl Client { + pub fn new(writer: OwnedWriteHalf, handle: JoinHandle<()>) -> Self { + let user = None; + Self { + handle, + user, + writer, + } + } + + pub fn unwrap(self) -> JoinHandle<()> { + let Self { + writer, + handle, + user, + } = self; + drop((writer, user)); + handle + } + + pub async fn send(&mut self, message: &str) { + self.writer.write_all(message.as_bytes()).await.unwrap(); + self.writer.write_all(b"\n").await.unwrap(); + } + pub fn set_user(&mut self, id: Option) { + self.user = id; + } + pub fn get_user(&self) -> Option { + self.user + } +} + async fn session(address: Addr, reader: OwnedReadHalf, remote: Remote) { let mut reader = BufReader::new(reader); loop { @@ -107,3 +176,24 @@ async fn session(address: Addr, reader: OwnedReadHalf, remote: Remote bool { + self.get_user(address).await.is_some() + } + + async fn get_user(&self, address: Addr) -> Option { + let (cmd, rec) = SessionCmd::new_get_user(address); + self.send(cmd); + rec.await.unwrap() + } +} + +impl SessionExt for Remote { + fn send(&self, cmd: SessionCmd) { + self.send(cmd).unwrap(); + } +} diff --git a/harsh-server/src/storage.rs b/harsh-server/src/storage.rs index c8ebd5e..0cc24d3 100644 --- a/harsh-server/src/storage.rs +++ b/harsh-server/src/storage.rs @@ -23,6 +23,12 @@ pub enum StorageCmd { UserSetName(Id, String), UserGetPass(Id, Sender>), UserSetPass(Id, String), + PermServerAddOp(Id), + PermServerRemoveOp(Id), + PermServerGetOp(Sender>), + PermChannelAddOp(Id, Id), + PermChannelRemoveOp(Id, Id), + PermChannelGetOp(Id, Sender>), } impl StorageCmd { @@ -110,6 +116,34 @@ impl StorageCmd { pub fn new_user_set_pass(id: Id, pass: String) -> Self { Self::UserSetPass(id, pass) } + + pub fn new_perm_server_add_op(user_id: Id) -> Self { + Self::PermServerAddOp(user_id) + } + + pub fn new_perm_server_remove_op(user_id: Id) -> Self { + Self::PermServerRemoveOp(user_id) + } + + pub fn new_perm_server_get_op() -> (Self, Receiver>) { + let (sender, receiver) = oneshot::channel(); + let cmd = Self::PermServerGetOp(sender); + (cmd, receiver) + } + + pub fn new_perm_channel_add_op(channel_id: Id, user_id: Id) -> Self { + Self::PermChannelAddOp(channel_id, user_id) + } + + pub fn new_perm_channel_remove_op(channel_id: Id, user_id: Id) -> Self { + Self::PermChannelRemoveOp(channel_id, user_id) + } + + pub fn new_perm_channel_get_op(channel_id: Id) -> (Self, Receiver>) { + let (sender, receiver) = oneshot::channel(); + let command = Self::PermChannelGetOp(channel_id, sender); + (command, receiver) + } } pub struct StorageProc { @@ -194,48 +228,32 @@ impl StorageProc { // // User // - UserList(sender) => { - let users = self.list("/users/"); - sender.send(users).unwrap(); - } + UserList(sender) => self.on_user_list(sender), + UserCreate(name, pass, sender) => self.on_user_create(name, pass, sender), + UserDelete(id) => self.on_user_delete(id), + UserGetName(id, sender) => self.on_user_get_name(id, sender), + UserSetName(id, name) => self.on_user_set_name(id, name), + UserGetPass(id, sender) => self.on_user_get_pass(id, sender), + UserSetPass(id, pass) => self.on_user_set_pass(id, pass), - UserCreate(name, pass, sender) => { - let user = User::new(name, pass); - let id = user.get_id(); - self.set(format!("/users/{id}"), user); - sender.send(id).unwrap(); + // + // Perms + // + PermServerGetOp(sender) => { + let result = self.list("/op/serv/".to_string()); + sender.send(result).unwrap(); } - - UserDelete(id) => { - self.remove(format!("/users/{id}")); + PermServerAddOp(user_id) => self.set(format!("/op/serv/{user_id}"), true), + PermServerRemoveOp(user_id) => self.remove(format!("/op/serv/{user_id}")), + PermChannelAddOp(channel_id, user_id) => { + self.set(format!("/op/channels/{channel_id}/{user_id}"), true) } - - UserGetName(id, sender) => { - let user = self.get::<_, User>(format!("/users/{id}")); - let name = user.map(|u| u.get_name().to_string()); - sender.send(name).unwrap(); + PermChannelRemoveOp(channel_id, user_id) => { + self.remove(format!("/op/channels/{channel_id}/{user_id}")) } - - UserSetName(id, name) => { - let path = format!("/users/{id}"); - if let Some(mut user) = self.get::<_, User>(&path) { - user.set_name(name); - self.set(path, user); - } - } - - UserGetPass(id, sender) => { - let user = self.get::<_, User>(format!("/users/{id}")); - let name = user.map(|u| u.get_pass().to_string()); - sender.send(name).unwrap(); - } - - UserSetPass(id, pass) => { - let path = format!("/users/{id}"); - if let Some(mut user) = self.get::<_, User>(&path) { - user.set_pass(pass); - self.set(path, user); - } + PermChannelGetOp(channel_id, sender) => { + let result = self.list(format!("/op/channels/{channel_id}/")); + sender.send(result).unwrap(); } }; } @@ -308,6 +326,54 @@ impl StorageProc { self.set(path, message); } } + + // + // User + // + + fn on_user_list(&mut self, sender: Sender>) { + let users = self.list("/users/"); + sender.send(users).unwrap(); + } + + fn on_user_create(&mut self, name: String, pass: String, sender: Sender) { + let user = User::new(name, pass); + let id = user.get_id(); + self.set(format!("/users/{id}"), user); + sender.send(id).unwrap(); + } + + fn on_user_delete(&mut self, id: Id) { + self.remove(format!("/users/{id}")); + } + + fn on_user_get_name(&mut self, id: Id, sender: Sender>) { + let user = self.get::<_, User>(format!("/users/{id}")); + let name = user.map(|u| u.get_name().to_string()); + sender.send(name).unwrap(); + } + + fn on_user_set_name(&mut self, id: Id, name: String) { + let path = format!("/users/{id}"); + if let Some(mut user) = self.get::<_, User>(&path) { + user.set_name(name); + self.set(path, user); + } + } + + fn on_user_get_pass(&mut self, id: Id, sender: Sender>) { + let user = self.get::<_, User>(format!("/users/{id}")); + let name = user.map(|u| u.get_pass().to_string()); + sender.send(name).unwrap(); + } + + fn on_user_set_pass(&mut self, id: Id, pass: String) { + let path = format!("/users/{id}"); + if let Some(mut user) = self.get::<_, User>(&path) { + user.set_pass(pass); + self.set(path, user); + } + } } #[telecomande::async_trait] @@ -323,7 +389,7 @@ impl Processor for StorageProc { } mod models; -pub use models::{Channel, Message, SerDeser, User}; +pub use models::{Channel, Message, Perm, SerDeser, User}; fn list(db: &Db, path: String) -> Vec { let len = path.len(); diff --git a/harsh-server/src/storage/models.rs b/harsh-server/src/storage/models.rs index 63cd8bd..386af5a 100644 --- a/harsh-server/src/storage/models.rs +++ b/harsh-server/src/storage/models.rs @@ -114,3 +114,9 @@ where db.insert(path, bytes).unwrap(); } } + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] +pub enum Perm { + OpServer, + OpChannel(Id), +}