From 88d4c3966b39ad0365e2aae866ffc8a724ad832e Mon Sep 17 00:00:00 2001 From: trivernis Date: Fri, 11 Sep 2020 19:13:25 +0200 Subject: [PATCH] Add method to refresh a request token Signed-off-by: trivernis --- Cargo.lock | 1 + Cargo.toml | 3 ++- src/database/tokens.rs | 31 ++++++++++++------------------- src/database/users.rs | 24 +++++++----------------- src/server/http_server.rs | 23 +++++++++++++++++++++-- src/server/messages.rs | 9 ++++++++- src/server/user_rpc.rs | 4 ++-- src/utils/mod.rs | 5 +++-- 8 files changed, 56 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6210156..fc58b0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -527,6 +527,7 @@ dependencies = [ name = "flotte-user-management" version = "0.1.0" dependencies = [ + "base64 0.12.3", "bcrypt", "byteorder", "colored", diff --git a/Cargo.toml b/Cargo.toml index 68b7bfe..a5cf68b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,4 +25,5 @@ colored = "2.0.0" crossbeam-utils = "0.7.2" mime = "0.3.16" serde_json = "1.0.57" -rouille = "3.0.0" \ No newline at end of file +rouille = "3.0.0" +base64 = "0.12.3" \ No newline at end of file diff --git a/src/database/tokens.rs b/src/database/tokens.rs index 5bf64af..f46b3ed 100644 --- a/src/database/tokens.rs +++ b/src/database/tokens.rs @@ -1,6 +1,6 @@ use crate::database::redis_operations::{EX, GET, SET, TTL}; -use crate::utils::create_user_token; use crate::utils::error::RedisConnection; +use crate::utils::{create_user_token, get_user_id_from_token}; use byteorder::{BigEndian, ByteOrder}; use redis::{ErrorKind, RedisError, RedisResult}; use serde::Serialize; @@ -12,8 +12,8 @@ const REFRESH_TOKEN_EXPIRE_SECONDS: i32 = 60 * 60 * 24; #[derive(Clone, Debug, Zeroize, Serialize)] #[zeroize(drop)] pub struct SessionTokens { - pub request_token: [u8; 32], - pub refresh_token: [u8; 32], + pub request_token: String, + pub refresh_token: String, pub request_ttl: i32, pub refresh_ttl: i32, } @@ -21,14 +21,14 @@ pub struct SessionTokens { impl SessionTokens { pub fn new(user_id: i32) -> Self { Self { - request_token: create_user_token(user_id), - refresh_token: create_user_token(user_id), + request_token: base64::encode(create_user_token(user_id)), + refresh_token: base64::encode(create_user_token(user_id)), request_ttl: REQUEST_TOKEN_EXPIRE_SECONDS, refresh_ttl: REFRESH_TOKEN_EXPIRE_SECONDS, } } - pub fn from_tokens(request_token: [u8; 32], refresh_token: [u8; 32]) -> Self { + pub fn from_tokens(request_token: String, refresh_token: String) -> Self { Self { request_token, refresh_token, @@ -39,27 +39,21 @@ impl SessionTokens { 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) + let request_token: String = redis::cmd(GET) .arg(&redis_request_key) .query(redis_connection)?; let redis_refresh_key = format!("user-{}_refresh", user_id); - let refresh_token_vec: Vec = redis::cmd(GET) + let refresh_token: String = redis::cmd(GET) .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 { + if request_token.len() == 0 { 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 { + if refresh_token.len() == 0 { return Err(RedisError::from(( ErrorKind::ResponseError, "No refresh token available", @@ -81,13 +75,12 @@ impl SessionTokens { } pub fn refresh(&mut self) { - self.request_token = create_user_token(self.get_user_id()); - self.refresh_token = create_user_token(self.get_user_id()); + self.request_token = base64::encode(create_user_token(self.get_user_id())); } /// Returns the user id that is stored in the first four bytes of the refresh token pub fn get_user_id(&self) -> i32 { - BigEndian::read_i32(&self.refresh_token[0..4]) + get_user_id_from_token(&self.refresh_token) } /// Saves the tokens into the database diff --git a/src/database/users.rs b/src/database/users.rs index fb1e55f..2d5bfe8 100644 --- a/src/database/users.rs +++ b/src/database/users.rs @@ -77,8 +77,8 @@ impl Users { pub fn create_get_tokens( &self, - email: String, - password: String, + email: &String, + password: &String, ) -> DatabaseResult { if self.validate_login(&email, password)? { let mut connection = self.database_connection.lock().unwrap(); @@ -99,10 +99,7 @@ impl Users { } } - pub fn validate_request_token( - &self, - token: &[u8; TOKEN_LENGTH], - ) -> DatabaseResult<(bool, i32)> { + pub fn validate_request_token(&self, token: &String) -> 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)?; @@ -110,10 +107,7 @@ impl Users { Ok((tokens.request_token == *token, tokens.request_ttl)) } - pub fn validate_refresh_token( - &self, - token: &[u8; TOKEN_LENGTH], - ) -> DatabaseResult<(bool, i32)> { + pub fn validate_refresh_token(&self, token: &String) -> 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)?; @@ -121,11 +115,8 @@ impl Users { Ok((tokens.refresh_token == *token, tokens.refresh_ttl)) } - pub fn refresh_tokens( - &self, - refresh_token: &[u8; TOKEN_LENGTH], - ) -> DatabaseResult { - let id = get_user_id_from_token(refresh_token); + pub fn refresh_tokens(&self, refresh_token: &String) -> 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)?; @@ -139,8 +130,7 @@ impl Users { } } - fn validate_login(&self, email: &String, password: String) -> DatabaseResult { - let password = Zeroizing::new(password); + fn validate_login(&self, email: &String, password: &String) -> DatabaseResult { let mut connection = self.database_connection.lock().unwrap(); let row = connection .query_opt( diff --git a/src/server/http_server.rs b/src/server/http_server.rs index f9ddccc..6b7b282 100644 --- a/src/server/http_server.rs +++ b/src/server/http_server.rs @@ -1,5 +1,5 @@ use crate::database::Database; -use crate::server::messages::LoginMessage; +use crate::server::messages::{LoginMessage, RefreshMessage}; use crate::utils::error::DBError; use rouille::{Request, Response, Server}; use serde::export::Formatter; @@ -60,6 +60,9 @@ impl UserHttpServer { (POST) (/login) => { Self::login(&database, request).unwrap_or_else(|e|Response::text(e.to_string())) }, + (POST) (/new-token) => { + Self::new_token(&database, request).unwrap_or_else(|e|Response::text(e.to_string())) + }, _ => Response::empty_404() ) }) @@ -76,7 +79,23 @@ impl UserHttpServer { .map_err(|e| HTTPError::new(e.to_string(), 400))?; let tokens = database .users - .create_get_tokens(login_request.email, login_request.password)?; + .create_get_tokens(&login_request.email, &login_request.password)?; + + Ok(Response::json(&tokens)) + } else { + Err(HTTPError::new("Missing Request Data".to_string(), 400)) + } + } + + fn new_token(database: &Database, request: &Request) -> HTTPResult { + if let Some(mut data) = request.data() { + let mut data_string = String::new(); + data.read_to_string(&mut data_string) + .map_err(|_| HTTPError::new("Failed to read request data".to_string(), 500))?; + let message: RefreshMessage = serde_json::from_str(data_string.as_str()) + .map_err(|e| HTTPError::new(e.to_string(), 400))?; + + let tokens = database.users.refresh_tokens(&message.refresh_token)?; Ok(Response::json(&tokens)) } else { diff --git a/src/server/messages.rs b/src/server/messages.rs index 3c719cb..cb17d09 100644 --- a/src/server/messages.rs +++ b/src/server/messages.rs @@ -9,7 +9,7 @@ use zeroize::Zeroize; #[derive(Deserialize)] pub struct TokenRequest { - pub token: [u8; 32], + pub token: String, } #[derive(Debug, Serialize)] @@ -77,7 +77,14 @@ pub struct CreatePermissionsRequest { } #[derive(Deserialize, Zeroize)] +#[zeroize(drop)] pub struct LoginMessage { pub email: String, pub password: String, } + +#[derive(Deserialize, Zeroize)] +#[zeroize(drop)] +pub struct RefreshMessage { + pub refresh_token: String, +} diff --git a/src/server/user_rpc.rs b/src/server/user_rpc.rs index 922fe67..9daf40c 100644 --- a/src/server/user_rpc.rs +++ b/src/server/user_rpc.rs @@ -79,13 +79,13 @@ impl UserRpcServer { "validate token", VALIDATE_TOKEN, "Validates a request token", - "{token: [u8; 32]}", + "{token: String}", ), InfoEntry::new( "get roles", GET_ROLES, "Returns the roles the user is assigned to", - "{token: [u8; 32]}", + "{token: String}", ), InfoEntry::new( "get permissions", diff --git a/src/utils/mod.rs b/src/utils/mod.rs index ddaf482..dfd6ae5 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -25,8 +25,9 @@ pub fn create_user_token(user_id: i32) -> [u8; TOKEN_LENGTH] { value } -pub fn get_user_id_from_token(token: &[u8]) -> i32 { - BigEndian::read_i32(token) +pub fn get_user_id_from_token(token: &String) -> i32 { + let token = base64::decode(&token).unwrap(); + BigEndian::read_i32(token.as_slice()) } pub fn hash_password(password: &[u8], salt: &[u8]) -> Result<[u8; 24], String> {