diff --git a/Cargo.toml b/Cargo.toml index ca17d65..9cbc32b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,3 +18,4 @@ r2d2_postgres = "0.18.0" ansi_term = "0.12" rust-crypto = "^0.2" base64 = "0.13.0" +base64-url = "1.4.10" diff --git a/src/database.rs b/src/database.rs index c523d70..adbefcf 100644 --- a/src/database.rs +++ b/src/database.rs @@ -17,7 +17,7 @@ static mut POOL: Option>> = None; pub fn init() -> Result<(), Error> { let manager = PostgresConnectionManager::new( - "host=localhost user=postgres dbname=test".parse().unwrap(), + "host=localhost user=postgres dbname=locutus".parse().unwrap(), NoTls, ); diff --git a/src/error.rs b/src/error.rs index 6f1777e..557c29c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -12,6 +12,7 @@ pub enum Kind { NotImplementedError, UsimpProtocolError, Utf8DecodeError, + AuthenticationError, } #[derive(Copy, Clone, Debug)] @@ -61,6 +62,7 @@ impl Error { Kind::NotImplementedError => "Not yet implemented", Kind::UsimpProtocolError => "USIMP protocol error", Kind::Utf8DecodeError => "Unable to decode UTF-8 data", + Kind::AuthenticationError => "Unable to authenticate", }, } } @@ -91,6 +93,7 @@ impl fmt::Display for Error { Kind::NotImplementedError => "not yet implemented", Kind::UsimpProtocolError => "usimp protocol error", Kind::Utf8DecodeError => "unable to decode utf-8 data", + Kind::AuthenticationError => "unable to authenticate", } .to_string(); if let Some(desc) = &self.desc { diff --git a/src/http/handler.rs b/src/http/handler.rs index f75277c..3f4a692 100644 --- a/src/http/handler.rs +++ b/src/http/handler.rs @@ -3,6 +3,7 @@ use crate::error::*; use crate::usimp; use crate::websocket; use serde_json; +use crate::usimp::Envelope; pub fn connection_handler(client: super::Stream) { let mut client = super::HttpStream { @@ -49,6 +50,8 @@ fn request_handler(client: &mut super::HttpStream) { return websocket::connection_handler(client, &req, res); } + // TODO check Content-Type == application/json + let mut error = None; let parts: Vec<&str> = req.uri.split('/').collect(); @@ -167,12 +170,48 @@ fn endpoint_handler( client.stream.read_exact(&mut buf[..length]).unwrap(); // TODO decompress - let input = match serde_json::from_slice(&buf[..length]) { + let data = match serde_json::from_slice(&buf[..length]) { Ok(val) => val, Err(e) => return error_handler(client, res, e.into()), }; - let buf = match usimp::endpoint(endpoint, input) { + let mut authorization = None; + if let Some(auth) = req.header.find_field("Authorization") { + authorization = Some(auth.split(" ").skip(1).collect()); + } + + let mut from_domain; + if let Some(from) = req.header.find_field("From-Domain") { + from_domain = from.to_string(); + } else { + return error_handler( + client, + res, + Error::new(Kind::UsimpProtocolError, Class::ClientError) + .set_desc("Unable to find field 'From-Domain'".to_string()) + ); + } + + let mut to_domain; + if let Some(to) = req.header.find_field("To-Domain") { + to_domain = to.to_string(); + } else { + return error_handler( + client, + res, + Error::new(Kind::UsimpProtocolError, Class::ClientError) + .set_desc("Unable to find field 'To-Domain'".to_string()) + ); + } + + let input = Envelope { + endpoint: endpoint.to_string(), + from_domain, + to_domain, + authorization, + data, + }; + let buf = match usimp::endpoint(input) { Ok(output) => output.to_string() + "\r\n", Err(e) => return error_handler(client, res, e), }; diff --git a/src/http/mod.rs b/src/http/mod.rs index 8057d07..2911c50 100644 --- a/src/http/mod.rs +++ b/src/http/mod.rs @@ -266,7 +266,7 @@ impl Response { buf = Some(new_buf); } - self.send(stream); + self.send(stream)?; if let Some(buf) = buf { stream.write_all(buf.as_bytes())?; diff --git a/src/usimp/mod.rs b/src/usimp/mod.rs index ba6d0ea..d7f4b0e 100644 --- a/src/usimp/mod.rs +++ b/src/usimp/mod.rs @@ -3,14 +3,38 @@ use serde_json; use crate::database; use crate::error::*; +use crypto::digest::Digest; -pub fn endpoint(endpoint: &str, input: serde_json::Value) -> Result { - match endpoint { - "echo" => Ok(serde_json::to_value(echo(serde_json::from_value(input)?)?)?), - _ => Err(Error::new(Kind::InvalidEndpointError, Class::ClientError)), +pub struct Envelope { + pub endpoint: String, + pub from_domain: String, + pub to_domain: String, + pub authorization: Option, + pub data: serde_json::Value, +} + +pub fn endpoint(envelope: Envelope) -> Result { + // TODO check authorization + // TODO domain_check + match envelope.endpoint.as_str() { + "echo" => Ok(serde_json::to_value(echo(serde_json::from_value(envelope.data)?)?)?), + "authorize" => Ok(serde_json::to_value(authorize(serde_json::from_value(envelope.data)?)?)?), + "notify" => Ok(serde_json::to_value(notify(serde_json::from_value(envelope.data)?)?)?), + _ => return Err(Error::new(Kind::InvalidEndpointError, Class::ClientError)), } } +pub fn get_id(input: &str) -> String { + let mut hasher = crypto::sha2::Sha256::new(); + hasher.input_str(chrono::Utc::now().timestamp_millis().to_string().as_str()); + hasher.input_str(" "); + hasher.input_str(input); + + let mut result = [0u8; 32]; + hasher.result(&mut result); + base64_url::encode(&result) +} + #[derive(Serialize, Deserialize)] pub struct EchoInput { message: String, @@ -38,3 +62,71 @@ pub fn echo(input: EchoInput) -> Result { } Ok(output) } + +#[derive(Serialize, Deserialize)] +pub struct AuthorizeInput { + r#type: String, + name: String, + password: String, +} + +#[derive(Serialize, Deserialize)] +pub struct AuthorizeOutput { + token: String, +} + +pub fn authorize(input: AuthorizeInput) -> Result { + let backend = database::client()?; + + let mut token; + match backend { + database::Client::Postgres(mut client) => { + let res = client.query("SELECT account_id FROM accounts WHERE name = $1", &[&input.name])?; + if res.len() == 0 { + return Err(Error::new(Kind::AuthenticationError, Class::ClientError)); + } + token = res.get(0).unwrap().get(0); + } + } + + Ok(AuthorizeOutput { token }) +} + +#[derive(Serialize, Deserialize)] +pub struct SendEventInput { + room_id: String, + data: serde_json::Value, +} + +#[derive(Serialize, Deserialize)] +pub struct SendEventOutput { + event_id: String, +} + +pub fn send_event(input: SendEventInput) -> Result { + let backend = database::client()?; + let event_id = get_id("hermann"); // TODO fix id generation + let data = serde_json::to_string(&input.data)?; + + match backend { + database::Client::Postgres(mut client) => { + client.execute("INSERT INTO events (event_id, room_id, data) VALUES ($1, $2, $3)", &[&event_id, &input.room_id, &data])?; + } + } + + Ok(SendEventOutput { event_id }) +} + +#[derive(Serialize, Deserialize)] +pub struct NotifyInput { + +} + +#[derive(Serialize, Deserialize)] +pub struct NotifyOutput { + +} + +pub fn notify(input: NotifyInput) -> Result { + Ok(NotifyOutput {}) +} diff --git a/src/websocket/handler.rs b/src/websocket/handler.rs index bcef3be..dfecb54 100644 --- a/src/websocket/handler.rs +++ b/src/websocket/handler.rs @@ -180,7 +180,7 @@ pub fn connection_handler( // TODO threads? let req: RequestEnvelope = serde_json::from_str(msg.data.as_str()).unwrap(); println!("Endpoint: {}, ReqNo: {}, Data: {}", req.endpoint, req.request_nr, req.data); - let a = usimp::endpoint(req.endpoint.as_str(), req.data).unwrap(); + //let a = usimp::endpoint(req.endpoint.as_str(), req.data).unwrap(); }, Message::CloseMessage(msg) => { println!("Received close frame: {}: {}", msg.code.unwrap_or(0), msg.reason.unwrap_or("-".to_string()));