Add some comments and fix some style issues

Signed-off-by: trivernis <trivernis@protonmail.com>
pull/1/head
trivernis 4 years ago
parent eb25371670
commit 11e725d302
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

@ -9,7 +9,6 @@ use postgres::NoTls;
use r2d2::Pool;
use r2d2_postgres::PostgresConnectionManager;
pub mod database_error;
pub mod models;
pub mod permissions;
pub mod role_permissions;
@ -32,6 +31,8 @@ pub trait Table {
fn init(&self) -> DatabaseResult<()>;
}
/// A structure that provides access to the databases and handles the creation of models.
/// Since it uses a Pool it can be cloned without losing the connection.
#[derive(Clone)]
pub struct Database {
pool: PostgresPool,

@ -2,6 +2,7 @@ use postgres::Row;
use serde::{Deserialize, Serialize};
use zeroize::Zeroize;
/// Record to store data in when retrieving rows from the users table
#[derive(Clone, Debug, Zeroize)]
#[zeroize(drop)]
pub struct UserRecord {
@ -24,6 +25,8 @@ impl UserRecord {
}
}
/// A row of the permission table that can be serialized and sent
/// via the rcp connection
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Permission {
pub id: i32,
@ -31,6 +34,8 @@ pub struct Permission {
pub description: String,
}
/// A row of the role table that can be serialized and sent
/// via the rcp connection
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Role {
pub id: i32,
@ -38,6 +43,9 @@ pub struct Role {
pub description: String,
}
/// A CreatePermissionEntry data structure that is used as an argument for the
/// bulk permission creation function of the Users Model and can directly be deserialized
/// from the corresponding rcp message.
#[derive(Deserialize)]
pub struct CreatePermissionsEntry {
pub name: String,

@ -2,6 +2,7 @@ use crate::database::models::{CreatePermissionsEntry, Permission};
use crate::database::{DatabaseResult, PostgresPool, Table, ADMIN_ROLE_NAME};
use crate::utils::error::DBError;
/// The permissions table that stores defined
#[derive(Clone)]
pub struct Permissions {
pool: PostgresPool,
@ -27,6 +28,8 @@ impl Table for Permissions {
}
impl Permissions {
/// Creates new permissions that are automatically assigned
/// to the admin role upon creation
pub fn create_permissions(
&self,
permissions: Vec<CreatePermissionsEntry>,

@ -2,6 +2,8 @@ use crate::database::models::Permission;
use crate::database::{DatabaseResult, PostgresPool, Table};
use crate::utils::error::DBError;
/// The m-n connection table for
/// roles and permissions
#[derive(Clone)]
pub struct RolePermissions {
pool: PostgresPool,
@ -28,6 +30,7 @@ impl Table for RolePermissions {
}
impl RolePermissions {
/// Returns all permissions for a role
pub fn by_role(&self, role_id: i32) -> DatabaseResult<Vec<Permission>> {
let mut connection = self.pool.get()?;
let rows = connection.query(

@ -3,6 +3,8 @@ use crate::database::role_permissions::RolePermissions;
use crate::database::{DatabaseResult, PostgresPool, Table, DEFAULT_ADMIN_EMAIL, ENV_ADMIN_EMAIL};
use crate::utils::error::DBError;
/// The role table that stores
/// all defined roles
#[derive(Clone)]
pub struct Roles {
pool: PostgresPool,
@ -32,6 +34,10 @@ impl Table for Roles {
}
impl Roles {
/// Creates a new role with the given permissions
/// that are then automatically assigned to the role
///
/// The role is automatically assigned to the default admin user
pub fn create_role(
&self,
name: String,

@ -8,6 +8,7 @@ use zeroize::Zeroize;
const REQUEST_TOKEN_EXPIRE_SECONDS: u32 = 60 * 10;
const REFRESH_TOKEN_EXPIRE_SECONDS: u32 = 60 * 60 * 24;
/// A struct to store session tokens of a user in a API-readable format
#[derive(Clone, Debug, Zeroize, Serialize)]
#[zeroize(drop)]
pub struct SessionTokens {
@ -18,6 +19,7 @@ pub struct SessionTokens {
}
impl SessionTokens {
/// Creates a new sessions token entry with newly generated tokens
pub fn new(user_id: i32) -> Self {
Self {
request_token: base64::encode(create_user_token(user_id)),
@ -27,7 +29,8 @@ impl SessionTokens {
}
}
pub fn from_tokens(request_token: String, refresh_token: String) -> Self {
/// Creates a sessions token entry with the given tokens
pub fn with_tokens(request_token: String, refresh_token: String) -> Self {
Self {
request_token,
refresh_token,
@ -36,6 +39,8 @@ impl SessionTokens {
}
}
/// Creates a new session tokens instance from a token store
/// entry
pub fn from_entry(other: &TokenStoreEntry) -> Option<Self> {
let request_token = other.request_token()?;
let refresh_token = other.refresh_token()?;
@ -47,6 +52,7 @@ impl SessionTokens {
})
}
/// Refreshes the request token
pub fn refresh(&mut self) {
self.request_token = base64::encode(create_user_token(self.get_user_id()));
}
@ -62,6 +68,10 @@ impl SessionTokens {
}
}
/// A store entry for tokens that keeps track of the token
/// expirations and provides an abstracted access to those.
/// The tokens are stored as their actual bytes representation
/// to decrease the memory impact
#[derive(Clone, Debug)]
pub struct TokenStoreEntry {
request_token: [u8; TOKEN_LENGTH],
@ -72,6 +82,8 @@ pub struct TokenStoreEntry {
}
impl TokenStoreEntry {
/// Creates a new token store entry with the given tokens
/// and sets the expiration to the configured maximum token lifetime
pub fn new(request_token: &String, refresh_token: &String) -> Result<Self, String> {
let request_token = base64::decode(request_token).unwrap();
let refresh_token = &base64::decode(refresh_token).unwrap();
@ -92,6 +104,9 @@ impl TokenStoreEntry {
})
}
/// Returns the ttl for the request token that is
/// calculated from the stored instant.
/// If the token is expired -1 is returned.
pub fn request_ttl(&self) -> i32 {
max(
(self.request_ttl - self.ttl_start.elapsed().as_secs() as u32) as i32,
@ -99,6 +114,9 @@ impl TokenStoreEntry {
)
}
/// Returns the ttl for the refresh token
/// that is calculated from the stored instant.
/// If the token is expired -1 is returned.
pub fn refresh_ttl(&self) -> i32 {
max(
(self.refresh_ttl - self.ttl_start.elapsed().as_secs() as u32) as i32,
@ -106,6 +124,7 @@ impl TokenStoreEntry {
)
}
/// Returns the request token if it hasn't expired
pub fn request_token(&self) -> Option<String> {
if self.request_ttl() > 0 {
Some(base64::encode(&self.request_token))
@ -114,6 +133,7 @@ impl TokenStoreEntry {
}
}
/// Returns the refresh token if it hasn't expired
pub fn refresh_token(&self) -> Option<String> {
if self.refresh_ttl() > 0 {
Some(base64::encode(&self.refresh_token))
@ -122,6 +142,8 @@ impl TokenStoreEntry {
}
}
/// Sets a new request token and resets
/// the expiration time for the request and refresh token
pub fn set_request_token(&mut self, token: String) -> i32 {
self.request_token
.copy_from_slice(base64::decode(token).unwrap().as_slice());
@ -132,6 +154,9 @@ impl TokenStoreEntry {
self.request_ttl as i32
}
/// Resets the timer that keeps track of the tokens expiration times
/// before resetting the current expiration times are stored so the ttl
/// for both tokens won't reset
fn reset_timer(&mut self) {
self.request_ttl = min(self.request_ttl(), 0) as u32;
self.refresh_ttl = min(self.refresh_ttl(), 0) as u32;
@ -151,7 +176,8 @@ impl TokenStore {
}
}
pub fn get_request_token(&self, request_token: &String) -> Option<&TokenStoreEntry> {
/// Returns the token store entry for a given request token
pub fn get_by_request_token(&self, request_token: &String) -> Option<&TokenStoreEntry> {
let user_id = get_user_id_from_token(&request_token);
if let Some(user_tokens) = self.tokens.get(&user_id) {
user_tokens.iter().find(|e| {
@ -165,7 +191,9 @@ impl TokenStore {
None
}
}
pub fn get_refresh_token(&self, refresh_token: &String) -> Option<&TokenStoreEntry> {
/// Returns the token store entry by the given refresh token
pub fn get_by_refresh_token(&self, refresh_token: &String) -> Option<&TokenStoreEntry> {
let user_id = get_user_id_from_token(&refresh_token);
if let Some(user_tokens) = self.tokens.get(&user_id) {
user_tokens.iter().find(|e| {
@ -179,6 +207,9 @@ impl TokenStore {
None
}
}
/// Sets the request token for a given refresh token
/// Also clears all expired token entries.
pub fn set_request_token(&mut self, refresh_token: &String, request_token: &String) {
self.clear_expired();
let user_id = get_user_id_from_token(&request_token);
@ -193,6 +224,7 @@ impl TokenStore {
}
}
/// Inserts a new pair of request and refresh token
pub fn insert(&mut self, request_token: &String, refresh_token: &String) -> Result<(), String> {
let user_id = get_user_id_from_token(refresh_token);
let user_tokens = if let Some(user_tokens) = self.tokens.get_mut(&user_id) {
@ -218,6 +250,7 @@ impl TokenStore {
Ok(())
}
/// Deletes all expired tokens from the store
pub fn clear_expired(&mut self) {
for (key, entry) in &self.tokens.clone() {
self.tokens.insert(

@ -2,6 +2,7 @@ use crate::database::models::Role;
use crate::database::{DatabaseResult, PostgresPool, Table};
use crate::utils::error::DBError;
/// A table that stores the relation between users and roles
#[derive(Clone)]
pub struct UserRoles {
pool: PostgresPool,
@ -28,6 +29,7 @@ impl Table for UserRoles {
}
impl UserRoles {
/// Returns all roles a user is asigned to
pub fn by_user(&self, user_id: i32) -> DatabaseResult<Vec<Role>> {
let mut connection = self.pool.get()?;
let rows = connection.query(

@ -9,6 +9,7 @@ use parking_lot::Mutex;
use std::sync::Arc;
use zeroize::{Zeroize, Zeroizing};
/// Table that stores users with their email addresses and hashed passwords
#[derive(Clone)]
pub struct Users {
pool: PostgresPool,
@ -41,6 +42,9 @@ impl Table for Users {
}
impl Users {
/// Creates a new user and returns an error if the user already exists.
/// When creating the user first a salt is generated, then the password is hashed
/// with BCrypt and the given salt. The salt and the hashed password are then stored into the database
pub fn create_user(
&self,
name: String,
@ -69,6 +73,8 @@ impl Users {
Ok(UserRecord::from_ordered_row(&row))
}
/// Creates new tokens for a user login that can be used by services
/// that need those tokens to verify a user login
pub fn create_tokens(
&self,
email: &String,
@ -88,9 +94,11 @@ impl Users {
}
}
/// Validates a request token and returns if it's valid and the
/// ttl of the token
pub fn validate_request_token(&self, token: &String) -> DatabaseResult<(bool, i32)> {
let store = self.token_store.lock();
let entry = store.get_request_token(&token);
let entry = store.get_by_request_token(&token);
if let Some(entry) = entry {
Ok((true, entry.request_ttl()))
@ -99,9 +107,10 @@ impl Users {
}
}
/// Validates a refresh token and returns if it's valid and the ttl
pub fn validate_refresh_token(&self, token: &String) -> DatabaseResult<(bool, i32)> {
let store = self.token_store.lock();
let entry = store.get_refresh_token(&token);
let entry = store.get_by_refresh_token(&token);
if let Some(entry) = entry {
Ok((true, entry.refresh_ttl()))
@ -110,9 +119,11 @@ impl Users {
}
}
/// Returns a new request token for a given refresh token
/// if the refresh token is valid
pub fn refresh_tokens(&self, refresh_token: &String) -> DatabaseResult<SessionTokens> {
let mut token_store = self.token_store.lock();
let tokens = token_store.get_refresh_token(refresh_token);
let tokens = token_store.get_by_refresh_token(refresh_token);
if let Some(mut tokens) = tokens.and_then(|t| SessionTokens::from_entry(t)) {
tokens.refresh();
tokens.store(&mut token_store)?;
@ -123,6 +134,8 @@ impl Users {
}
}
/// Validates the login data of the user by creating the hash for the given password
/// and comparing it with the database entry
fn validate_login(&self, email: &String, password: &String) -> DatabaseResult<bool> {
let mut connection = self.pool.get()?;
let row = connection

@ -11,13 +11,19 @@ use std::thread::Builder;
fn main() {
init_logger();
// Create a new database and initialize it
let database = Database::new().unwrap();
database.init().unwrap();
// Create the required servers
let rpc_server = UserRpcServer::new(&database);
let http_server = UserHttpServer::new(&database);
// Create a new waitgroup that is used to wait for both servers to exit
let wg = WaitGroup::new();
{
let wg = WaitGroup::clone(&wg);
// Build a thread named rpc and start the rpc server inside of it
Builder::new()
.name("rpc".to_string())
.spawn(move || {
@ -28,7 +34,7 @@ fn main() {
}
{
let wg = WaitGroup::clone(&wg);
let http_server = http_server;
// Build a new http thread and start the http server inside of it
Builder::new()
.name("http".to_string())
.spawn(move || {
@ -37,9 +43,13 @@ fn main() {
})
.unwrap();
}
// Wait for both servers to exit
wg.wait();
}
/// Initializes the env_logger with a custom format
/// that also logs the thread names
fn init_logger() {
env_logger::Builder::from_env(Env::default().default_filter_or("info"))
.format(|buf, record| {

@ -10,6 +10,8 @@ use std::io::Read;
const LISTEN_ADDRESS: &str = "HTTP_SERVER_ADDRESS";
const DEFAULT_LISTEN_ADDRESS: &str = "127.0.0.1:8080";
/// The HTTP server of the user management that provides a
/// REST api for login and requesting tokens
pub struct UserHttpServer {
database: Database,
}
@ -57,6 +59,8 @@ impl UserHttpServer {
}
}
/// Stats the server.
/// This call blocks until the server is shut down.
pub fn start(&self) {
log::info!("Starting HTTP-Server...");
let listen_address =
@ -78,6 +82,7 @@ impl UserHttpServer {
server.run()
}
/// Handles the login part of the REST api
fn login(database: &Database, request: &Request) -> HTTPResult<Response> {
if let Some(mut data) = request.data() {
let mut data_string = String::new();
@ -95,6 +100,7 @@ impl UserHttpServer {
}
}
/// Handles the new token part of the rest api
fn new_token(database: &Database, request: &Request) -> HTTPResult<Response> {
if let Some(mut data) = request.data() {
let mut data_string = String::new();

@ -17,6 +17,9 @@ use std::thread::Builder;
const RPC_SERVER_ADDRESS: &str = "RPC_SERVER_ADDRESS";
const DEFAULT_SERVER_ADDRESS: &str = "127.0.0.1:5555";
/// The RPC server that provides an interface
/// for applications to validate request tokens
/// and request the assigned roles
pub struct UserRpcServer {
database: Database,
}
@ -30,6 +33,7 @@ impl UserRpcServer {
}
}
/// Stats the user rpc server with 2 x num-cpus threads.
pub fn start(&self) {
let listen_address =
dotenv::var(RPC_SERVER_ADDRESS).unwrap_or(DEFAULT_SERVER_ADDRESS.to_string());
@ -70,6 +74,7 @@ impl UserRpcServer {
}
}
/// Handles the validation of request tokens
fn handle_validate_token(database: Database, data: &Vec<u8>) -> RpcResult<Message> {
log::trace!("Validating token.");
let message = TokenRequest::deserialize(&mut Deserializer::new(&mut data.as_slice()))
@ -84,6 +89,7 @@ impl UserRpcServer {
Ok(Message::new(VALIDATE_TOKEN, data))
}
/// Handles a INFO message that returns all valid methods of the rpc sserver
fn handle_info() -> RpcResult<Message> {
log::trace!("Get Info");
Ok(Message::new_with_serialize(
@ -124,6 +130,7 @@ impl UserRpcServer {
))
}
/// Returns all permissions of a role
fn handle_get_permissions(database: Database, data: &Vec<u8>) -> RpcResult<Message> {
log::trace!("Get Permissions");
let message =
@ -141,6 +148,7 @@ impl UserRpcServer {
))
}
/// Returns all roles of a user
fn handle_get_roles(database: Database, data: &Vec<u8>) -> RpcResult<Message> {
log::trace!("Get Roles");
let message = TokenRequest::deserialize(&mut Deserializer::new(&mut data.as_slice()))
@ -159,6 +167,7 @@ impl UserRpcServer {
Ok(Message::new_with_serialize(GET_ROLES, response_data))
}
/// Handles the requests for creating new roles
fn handle_create_role(database: Database, data: &Vec<u8>) -> RpcResult<Message> {
log::trace!("Create Role");
let message = CreateRoleRequest::deserialize(&mut Deserializer::new(&mut data.as_slice()))
@ -171,6 +180,7 @@ impl UserRpcServer {
Ok(Message::new_with_serialize(CREATE_ROLE, role))
}
/// Handles the request for creating new permissions.
fn handle_create_permissions(database: Database, data: &Vec<u8>) -> RpcResult<Message> {
log::trace!("Create Permission");
let message =

@ -8,6 +8,7 @@ pub mod error;
pub const TOKEN_LENGTH: usize = 32;
const SALT_LENGTH: usize = 16;
/// Creates a new random salt
pub fn create_salt() -> [u8; SALT_LENGTH] {
let mut rng = rand::thread_rng();
let mut salt = [0u8; SALT_LENGTH];
@ -16,6 +17,8 @@ pub fn create_salt() -> [u8; SALT_LENGTH] {
salt
}
/// Creates a new random user token where the first 4 bytes represent
/// the userId
pub fn create_user_token(user_id: i32) -> [u8; TOKEN_LENGTH] {
let mut rng = rand::thread_rng();
let mut value = [0u8; TOKEN_LENGTH];
@ -25,11 +28,13 @@ pub fn create_user_token(user_id: i32) -> [u8; TOKEN_LENGTH] {
value
}
/// Extracts the userId from a request token
pub fn get_user_id_from_token(token: &String) -> i32 {
let token = base64::decode(&token).unwrap();
BigEndian::read_i32(token.as_slice())
}
/// Hashes a password with a salt by using BCrypt
pub fn hash_password(password: &[u8], salt: &[u8]) -> Result<[u8; 24], String> {
panic::catch_unwind(|| {
let mut pw_hash = [0u8; 24];

Loading…
Cancel
Save