diff --git a/src/database/database_error.rs b/src/database/database_error.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/database/mod.rs b/src/database/mod.rs index f80ad3b..bbb231b 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -3,15 +3,15 @@ use crate::database::role_permissions::RolePermissions; use crate::database::roles::Roles; use crate::database::user_roles::UserRoles; use crate::database::users::Users; +use crate::utils::error::{ + DBError, DatabaseClient, DatabaseResult, PostgresError, RedisClient, RedisConnection, +}; use dotenv; use postgres::{Client, NoTls}; -use redis::{RedisError, RedisResult}; -use serde::export::Formatter; -use std::error; -use std::fmt; -use std::fmt::Display; +use redis::RedisResult; use std::sync::{Arc, Mutex}; +pub mod database_error; pub mod models; pub mod permissions; pub mod redis_operations; @@ -26,11 +26,6 @@ const DEFAULT_CONNECTION: &str = "postgres://postgres:postgres@localhost/postgre const REDIS_CONNECTION_URL: &str = "REDIS_CONNECTION_URL"; const DEFAULT_REDIS_CONNECTION: &str = "redis:://127.0.0.1/"; -pub type DatabaseClient = postgres::Client; -pub type RedisClient = redis::Client; -pub type RedisConnection = redis::Connection; -pub type PostgresError = postgres::Error; - pub trait Table { fn new( database_connection: Arc>, @@ -39,27 +34,6 @@ pub trait Table { fn init(&self) -> DatabaseResult<()>; } -#[derive(Debug)] -pub enum Error { - Redis(RedisError), - Postgres(PostgresError), - RecordExists, - ScryptError, - DeserializeError(serde_postgres::DeError), - GenericError(String), -} - -impl Display for Error { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.to_string()) - } -} - -impl error::Error for Error {} - -pub type DatabaseError = Error; -pub type DatabaseResult = Result; - #[derive(Clone)] pub struct Database { database_connection: Arc>, @@ -74,10 +48,10 @@ pub struct Database { impl Database { pub fn new() -> DatabaseResult { let database_connection = Arc::new(Mutex::new( - get_database_connection().map_err(|e| Error::Postgres(e))?, + get_database_connection().map_err(|e| DBError::Postgres(e))?, )); let redis_connection = Arc::new(Mutex::new( - get_redis_connection().map_err(|e| Error::Redis(e))?, + get_redis_connection().map_err(|e| DBError::Redis(e))?, )); Ok(Self { users: Users::new( diff --git a/src/database/models.rs b/src/database/models.rs index 7ccffba..0736acb 100644 --- a/src/database/models.rs +++ b/src/database/models.rs @@ -30,3 +30,10 @@ pub struct Permission { pub name: String, pub description: String, } + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Role { + pub id: i32, + pub name: String, + pub description: String, +} diff --git a/src/database/permissions.rs b/src/database/permissions.rs index e186908..13d3002 100644 --- a/src/database/permissions.rs +++ b/src/database/permissions.rs @@ -1,4 +1,5 @@ -use crate::database::{DatabaseClient, DatabaseError, DatabaseResult, RedisConnection, Table}; +use crate::database::{DatabaseClient, DatabaseResult, RedisConnection, Table}; +use crate::utils::error::DBError; use postgres::Client; use std::sync::{Arc, Mutex}; @@ -30,6 +31,6 @@ impl Table for Permissions { description VARCHAR(512) );", ) - .map_err(|e| DatabaseError::Postgres(e)) + .map_err(DBError::from) } } diff --git a/src/database/role_permissions.rs b/src/database/role_permissions.rs index 5dc2c2b..55fe2db 100644 --- a/src/database/role_permissions.rs +++ b/src/database/role_permissions.rs @@ -1,5 +1,6 @@ use crate::database::models::Permission; -use crate::database::{DatabaseClient, DatabaseError, DatabaseResult, RedisConnection, Table}; +use crate::database::{DatabaseClient, DatabaseResult, RedisConnection, Table}; +use crate::utils::error::DBError; use std::sync::{Arc, Mutex}; #[derive(Clone)] @@ -31,14 +32,17 @@ impl Table for RolePermissions { PRIMARY KEY (role_id, permission_id) );", ) - .map_err(|e| DatabaseError::Postgres(e)) + .map_err(DBError::from) } } impl RolePermissions { pub fn by_role(&self, role_id: i32) -> DatabaseResult> { let mut connection = self.database_connection.lock().unwrap(); - let rows = connection.query("SELECT * FROM role_permissions, permissions WHERE role_id = $1 AND role_permissions.permission_id = permissions.id", &[&role_id]).map_err(|e|DatabaseError::Postgres(e))?; - serde_postgres::from_rows(&rows).map_err(|e| DatabaseError::DeserializeError(e)) + let rows = connection.query( + "SELECT * FROM role_permissions, permissions WHERE role_id = $1 AND role_permissions.permission_id = permissions.id", + &[&role_id])?; + + serde_postgres::from_rows(&rows).map_err(DBError::from) } } diff --git a/src/database/roles.rs b/src/database/roles.rs index ceb5a73..bc39ebc 100644 --- a/src/database/roles.rs +++ b/src/database/roles.rs @@ -1,5 +1,6 @@ use crate::database::role_permissions::RolePermissions; -use crate::database::{DatabaseError, DatabaseResult, RedisConnection, Table}; +use crate::database::{DatabaseResult, RedisConnection, Table}; +use crate::utils::error::DBError; use postgres::Client; use std::sync::{Arc, Mutex}; @@ -37,6 +38,6 @@ impl Table for Roles { description VARCHAR(512) );", ) - .map_err(|e| DatabaseError::Postgres(e)) + .map_err(DBError::from) } } diff --git a/src/database/tokens.rs b/src/database/tokens.rs index 3e91859..b8c97b3 100644 --- a/src/database/tokens.rs +++ b/src/database/tokens.rs @@ -1,8 +1,8 @@ -use crate::database::redis_operations::{EX, GET, SET}; -use crate::database::RedisConnection; +use crate::database::redis_operations::{EX, GET, SET, TTL}; use crate::utils::create_user_token; +use crate::utils::error::RedisConnection; use byteorder::{BigEndian, ByteOrder}; -use redis::RedisResult; +use redis::{ErrorKind, RedisError, RedisResult}; use zeroize::Zeroize; const REQUEST_TOKEN_EXPIRE_SECONDS: usize = 60 * 10; @@ -13,6 +13,8 @@ const REFRESH_TOKEN_EXPIRE_SECONDS: usize = 60 * 60 * 24; pub struct SessionTokens { pub request_token: [u8; 32], pub refresh_token: [u8; 32], + pub request_ttl: i32, + pub refresh_ttl: i32, } impl SessionTokens { @@ -20,6 +22,8 @@ impl SessionTokens { Self { request_token: create_user_token(user_id), refresh_token: create_user_token(user_id), + request_ttl: -1, + refresh_ttl: -1, } } @@ -27,31 +31,51 @@ impl SessionTokens { Self { request_token, refresh_token, + request_ttl: -1, + refresh_ttl: -1, } } pub fn retrieve(user_id: i32, redis_connection: &mut RedisConnection) -> RedisResult { let redis_request_key = format!("user-{}_request", user_id); let request_token_vec: Vec = redis::cmd(GET) - .arg(redis_request_key) + .arg(&redis_request_key) .query(redis_connection)?; let redis_refresh_key = format!("user-{}_refresh", user_id); let refresh_token_vec: Vec = redis::cmd(GET) - .arg(redis_refresh_key) + .arg(&redis_refresh_key) .query(redis_connection)?; let mut request_token = [0u8; 32]; let mut refresh_token = [0u8; 32]; if request_token_vec.len() == 32 { request_token.copy_from_slice(&request_token_vec); + } else { + return Err(RedisError::from(( + ErrorKind::ResponseError, + "No refresh token available", + ))); } if refresh_token_vec.len() == 32 { refresh_token.copy_from_slice(&refresh_token_vec); + } else { + return Err(RedisError::from(( + ErrorKind::ResponseError, + "No refresh token available", + ))); } + let request_ttl: i32 = redis::cmd(TTL) + .arg(&redis_request_key) + .query(redis_connection)?; + let refresh_ttl: i32 = redis::cmd(TTL) + .arg(&redis_refresh_key) + .query(redis_connection)?; Ok(Self { request_token, refresh_token, + request_ttl, + refresh_ttl, }) } diff --git a/src/database/user_roles.rs b/src/database/user_roles.rs index cc71767..cb3c149 100644 --- a/src/database/user_roles.rs +++ b/src/database/user_roles.rs @@ -1,4 +1,6 @@ -use crate::database::{DatabaseError, DatabaseResult, RedisConnection, Table}; +use crate::database::models::Role; +use crate::database::{DatabaseResult, RedisConnection, Table}; +use crate::utils::error::DBError; use postgres::Client; use std::sync::{Arc, Mutex}; @@ -31,6 +33,18 @@ impl Table for UserRoles { PRIMARY KEY (user_id, role_id) );", ) - .map_err(|e| DatabaseError::Postgres(e)) + .map_err(DBError::from) + } +} + +impl UserRoles { + pub fn by_user(&self, user_id: i32) -> DatabaseResult> { + let mut connection = self.database_connection.lock().unwrap(); + let rows = connection.query( + "SELECT * FROM user_roles, roles WHERE user_id = $1 AND roles.id = user_roles.role_id", + &[&user_id], + )?; + + serde_postgres::from_rows(&rows).map_err(DBError::from) } } diff --git a/src/database/users.rs b/src/database/users.rs index 834a697..2abfff2 100644 --- a/src/database/users.rs +++ b/src/database/users.rs @@ -1,7 +1,8 @@ use crate::database::models::UserRecord; use crate::database::tokens::SessionTokens; use crate::database::user_roles::UserRoles; -use crate::database::{DatabaseError, DatabaseResult, RedisConnection, Table}; +use crate::database::{DatabaseResult, RedisConnection, Table}; +use crate::utils::error::DBError; use crate::utils::{create_salt, get_user_id_from_token, TOKEN_LENGTH}; use postgres::Client; use scrypt::ScryptParams; @@ -43,7 +44,7 @@ impl Table for Users { salt BYTEA NOT NULL );", ) - .map_err(|e| DatabaseError::Postgres(e)) + .map_err(DBError::from) } } @@ -58,11 +59,10 @@ impl Users { let mut password = Zeroizing::new(password); if !connection - .query("SELECT email FROM users WHERE email = $1", &[&email]) - .map_err(|e| DatabaseError::Postgres(e))? + .query("SELECT email FROM users WHERE email = $1", &[&email])? .is_empty() { - return Err(DatabaseError::RecordExists); + return Err(DBError::RecordExists); } let salt = Zeroizing::new(create_salt()); let mut pw_hash = Zeroizing::new([0u8; 32]); @@ -72,11 +72,11 @@ impl Users { &ScryptParams::recommended(), &mut *pw_hash, ) - .map_err(|_| DatabaseError::ScryptError)?; + .map_err(|_| DBError::ScryptError)?; password.zeroize(); let row = connection.query_one(" INSERT INTO users (name, email, password_hash, salt) VALUES ($1, $2, $3, $4) RETURNING *; - ", &[&name, &email, &pw_hash.to_vec(), &salt.to_vec()]).map_err(|e|DatabaseError::Postgres(e))?; + ", &[&name, &email, &pw_hash.to_vec(), &salt.to_vec()])?; Ok(UserRecord::from_ordered_row(&row)) } @@ -84,39 +84,39 @@ impl Users { pub fn create_token(&self, email: String, password: String) -> DatabaseResult { if self.validate_login(&email, password)? { let mut connection = self.database_connection.lock().unwrap(); - let row = connection - .query_one("SELECT id FROM users WHERE email = $1", &[&email]) - .map_err(|e| DatabaseError::Postgres(e))?; + let row = connection.query_one("SELECT id FROM users WHERE email = $1", &[&email])?; let id: i32 = row.get(0); let mut redis_connection = self.redis_connection.lock().unwrap(); let tokens = SessionTokens::new(id); - tokens - .store(&mut redis_connection) - .map_err(|e| DatabaseError::Redis(e))?; + tokens.store(&mut redis_connection)?; Ok(tokens) } else { - Err(DatabaseError::GenericError("Invalid password".to_string())) + Err(DBError::GenericError("Invalid password".to_string())) } } - pub fn validate_request_token(&self, token: &[u8; TOKEN_LENGTH]) -> DatabaseResult { + pub fn validate_request_token( + &self, + token: &[u8; TOKEN_LENGTH], + ) -> DatabaseResult<(bool, i32)> { let id = get_user_id_from_token(token); let mut redis_connection = self.redis_connection.lock().unwrap(); - let tokens = SessionTokens::retrieve(id, &mut redis_connection) - .map_err(|e| DatabaseError::Redis(e))?; + let tokens = SessionTokens::retrieve(id, &mut redis_connection)?; - Ok(tokens.request_token == *token) + Ok((tokens.request_token == *token, tokens.request_ttl)) } - pub fn validate_refresh_token(&self, token: &[u8; TOKEN_LENGTH]) -> DatabaseResult { + pub fn validate_refresh_token( + &self, + token: &[u8; TOKEN_LENGTH], + ) -> DatabaseResult<(bool, i32)> { let id = get_user_id_from_token(token); let mut redis_connection = self.redis_connection.lock().unwrap(); - let tokens = SessionTokens::retrieve(id, &mut redis_connection) - .map_err(|e| DatabaseError::Redis(e))?; + let tokens = SessionTokens::retrieve(id, &mut redis_connection)?; - Ok(tokens.refresh_token == *token) + Ok((tokens.refresh_token == *token, tokens.refresh_ttl)) } pub fn refresh_tokens( @@ -125,32 +125,25 @@ impl Users { ) -> DatabaseResult { let id = get_user_id_from_token(refresh_token); let mut redis_connection = self.redis_connection.lock().unwrap(); - let mut tokens = SessionTokens::retrieve(id, &mut redis_connection) - .map_err(|e| DatabaseError::Redis(e))?; + let mut tokens = SessionTokens::retrieve(id, &mut redis_connection)?; if tokens.refresh_token == *refresh_token { tokens.refresh(); - tokens - .store(&mut redis_connection) - .map_err(|e| DatabaseError::Redis(e))?; + tokens.store(&mut redis_connection)?; Ok(tokens) } else { - Err(DatabaseError::GenericError( - "Invalid refresh token!".to_string(), - )) + Err(DBError::GenericError("Invalid refresh token!".to_string())) } } fn validate_login(&self, email: &String, password: String) -> DatabaseResult { let password = Zeroizing::new(password); let mut connection = self.database_connection.lock().unwrap(); - let row = connection - .query_one( - "SELECT password_hash, salt FROM users WHERE email = $1", - &[&email], - ) - .map_err(|e| DatabaseError::Postgres(e))?; + let row = connection.query_one( + "SELECT password_hash, salt FROM users WHERE email = $1", + &[&email], + )?; let original_pw_hash: Zeroizing> = Zeroizing::new(row.get(0)); let salt: Zeroizing> = Zeroizing::new(row.get(1)); let mut pw_hash = Zeroizing::new([0u8; 32]); @@ -161,7 +154,7 @@ impl Users { &ScryptParams::recommended(), &mut *pw_hash, ) - .map_err(|_| DatabaseError::ScryptError)?; + .map_err(|_| DBError::ScryptError)?; Ok(*pw_hash == *original_pw_hash.as_slice()) } diff --git a/src/server/messages.rs b/src/server/messages.rs index 853249d..ff5ffd3 100644 --- a/src/server/messages.rs +++ b/src/server/messages.rs @@ -1,3 +1,4 @@ +use crate::utils::error::DBError; use serde::export::Formatter; use serde::{Deserialize, Serialize}; use std::error::Error; @@ -5,7 +6,7 @@ use std::fmt; use std::fmt::Display; #[derive(Deserialize)] -pub struct ValidateTokenRequest { +pub struct TokenRequest { pub token: [u8; 32], } @@ -27,16 +28,26 @@ impl Display for ErrorMessage { } impl Error for ErrorMessage {} +impl From for ErrorMessage { + fn from(other: DBError) -> Self { + Self::new(other.to_string()) + } +} + #[derive(Serialize)] pub struct InfoEntry { name: String, - method: [u8; 4], + method: String, description: String, data: String, } impl InfoEntry { pub fn new(name: &str, method: [u8; 4], description: &str, data: &str) -> Self { + let method = format!( + "0x{:x} 0x{:x} 0x{:x} 0x{:x}", + method[0], method[1], method[2], method[3] + ); Self { method, name: name.to_string(), diff --git a/src/server/user_rpc.rs b/src/server/user_rpc.rs index c02e93c..217b02e 100644 --- a/src/server/user_rpc.rs +++ b/src/server/user_rpc.rs @@ -1,8 +1,7 @@ use super::rpc_methods::*; use crate::database::Database; -use crate::server::messages::{ - ErrorMessage, GetPermissionsRequest, InfoEntry, ValidateTokenRequest, -}; +use crate::server::messages::{ErrorMessage, GetPermissionsRequest, InfoEntry, TokenRequest}; +use crate::utils::get_user_id_from_token; use msgrpc::message::Message; use msgrpc::server::RpcServer; use rmp_serde::Deserializer; @@ -39,7 +38,7 @@ impl UserRpcServer { let mut handler = h.lock().unwrap(); let response = match handler.message.method { INFO => self.handle_info(), - GET_ROLES => unimplemented!(), + GET_ROLES => self.handle_get_roles(&handler.message.data), VALIDATE_TOKEN => self.handle_validate_token(&handler.message.data), GET_ROLE_PERMISSIONS => self.handle_get_permissions(&handler.message.data), _ => Err(ErrorMessage::new("Invalid Method".to_string())), @@ -52,14 +51,13 @@ impl UserRpcServer { fn handle_validate_token(&self, data: &Vec) -> RpcResult { log::trace!("Validating token."); - let message = - ValidateTokenRequest::deserialize(&mut Deserializer::new(&mut data.as_slice())) - .map_err(|e| ErrorMessage::new(e.to_string()))?; + let message = TokenRequest::deserialize(&mut Deserializer::new(&mut data.as_slice())) + .map_err(|e| ErrorMessage::new(e.to_string()))?; let valid = self .database .users .validate_request_token(&message.token) - .unwrap_or(false); + .unwrap_or((false, -1)); log::trace!("Serializing..."); let data = rmp_serde::to_vec(&valid).map_err(|e| ErrorMessage::new(e.to_string()))?; @@ -67,6 +65,7 @@ impl UserRpcServer { } fn handle_info(&self) -> RpcResult { + log::trace!("Get Info"); Ok(Message::new_with_serialize( INFO, vec![ @@ -86,7 +85,7 @@ impl UserRpcServer { InfoEntry::new( "get permissions", GET_ROLE_PERMISSIONS, - "Returns all permissions the givenroles are assigned to", + "Returns all permissions the given roles are assigned to", "{role_ids: [i32]}", ), ], @@ -94,16 +93,13 @@ impl UserRpcServer { } fn handle_get_permissions(&self, data: &Vec) -> RpcResult { + log::trace!("Get Permissions"); let message = GetPermissionsRequest::deserialize(&mut Deserializer::new(&mut data.as_slice())) .map_err(|e| ErrorMessage::new(e.to_string()))?; let mut response_data = HashMap::new(); for role_id in message.role_ids { - let permissions = self - .database - .role_permission - .by_role(role_id) - .map_err(|e| ErrorMessage::new(e.to_string()))?; + let permissions = self.database.role_permission.by_role(role_id)?; response_data.insert(role_id.to_string(), permissions); } @@ -112,4 +108,23 @@ impl UserRpcServer { response_data, )) } + + fn handle_get_roles(&self, data: &Vec) -> RpcResult { + log::trace!("Get Roles"); + let message = TokenRequest::deserialize(&mut Deserializer::new(&mut data.as_slice())) + .map_err(|e| ErrorMessage::new(e.to_string()))?; + if !self + .database + .users + .validate_request_token(&message.token) + .unwrap_or((false, -1)) + .0 + { + return Err(ErrorMessage::new("Invalid request token".to_string())); + } + let user_id = get_user_id_from_token(&message.token); + let response_data = self.database.user_roles.by_user(user_id)?; + + Ok(Message::new_with_serialize(GET_ROLES, response_data)) + } } diff --git a/src/utils/error.rs b/src/utils/error.rs new file mode 100644 index 0000000..83d3e5e --- /dev/null +++ b/src/utils/error.rs @@ -0,0 +1,47 @@ +use redis::RedisError; +use serde_postgres::DeError; +use std::error; +use std::fmt::{self, Display, Formatter}; + +#[derive(Debug)] +pub enum DBError { + Redis(RedisError), + Postgres(PostgresError), + RecordExists, + ScryptError, + DeserializeError(serde_postgres::DeError), + GenericError(String), +} + +impl Display for DBError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.to_string()) + } +} + +impl error::Error for DBError {} + +pub type DatabaseResult = Result; + +impl From for DBError { + fn from(other: PostgresError) -> Self { + Self::Postgres(other) + } +} + +impl From for DBError { + fn from(other: RedisError) -> Self { + Self::Redis(other) + } +} + +impl From for DBError { + fn from(other: DeError) -> Self { + Self::DeserializeError(other) + } +} + +pub type DatabaseClient = postgres::Client; +pub type RedisClient = redis::Client; +pub type RedisConnection = redis::Connection; +pub type PostgresError = postgres::Error; diff --git a/src/utils.rs b/src/utils/mod.rs similarity index 97% rename from src/utils.rs rename to src/utils/mod.rs index e1a4784..22f8cc9 100644 --- a/src/utils.rs +++ b/src/utils/mod.rs @@ -1,6 +1,8 @@ use byteorder::{BigEndian, ByteOrder}; use rand::Rng; +pub mod error; + pub const TOKEN_LENGTH: usize = 32; const SALT_LENGTH: usize = 16;