diff --git a/Cargo.lock b/Cargo.lock index 029140d..eb0a8e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -529,6 +529,7 @@ dependencies = [ "homedisk-utils", "hyper", "log", + "rust_utilities", "serde", "thiserror", "tower-http", @@ -540,9 +541,7 @@ version = "0.0.0" dependencies = [ "anyhow", "axum", - "chrono", "dirs", - "jsonwebtoken", "serde", "thiserror", "toml", @@ -555,11 +554,9 @@ name = "homedisk-utils" version = "0.0.0" dependencies = [ "futures-util", - "hex", "log", + "rust_utilities", "serde", - "sha-1", - "sha2", "sqlx", "tokio", "uuid", @@ -1072,6 +1069,20 @@ dependencies = [ "winapi", ] +[[package]] +name = "rust_utilities" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8524c76496ed3097b6d24d7cb3b435cc8a84cbded6da7153942f6daa3ade9da7" +dependencies = [ + "chrono", + "hex", + "jsonwebtoken", + "serde", + "sha-1", + "sha2", +] + [[package]] name = "rustc-demangle" version = "0.1.21" diff --git a/config.toml b/config.toml index e6c1b74..4813cd4 100644 --- a/config.toml +++ b/config.toml @@ -1,3 +1,4 @@ +# HTTP Server [http] host = "0.0.0.0" port = 8080 @@ -6,5 +7,7 @@ cors = [ "localhost:8000", ] +# Json Web Token [jwt] -secret = "secret key used to sign tokens" +secret = "secret key used to sign tokens" # secret used to sign tokens +expires = 24 # validity of token in hours diff --git a/server/Cargo.toml b/server/Cargo.toml index 0f0bbe6..403bf58 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -20,10 +20,14 @@ features = ["full"] version = "1.0.136" features = ["derive"] +[dependencies.rust_utilities] +version = "0.2.0" +features = ["jsonwebtoken"] + [dependencies.homedisk-utils] path = "../utils" -features = ["full"] +features = ["database"] [dependencies.homedisk-types] path = "../types" -features = ["axum", "token"] +features = ["axum"] diff --git a/server/src/auth/login.rs b/server/src/auth/login.rs index 19e4043..ba1db25 100644 --- a/server/src/auth/login.rs +++ b/server/src/auth/login.rs @@ -3,9 +3,9 @@ use homedisk_types::{ auth::login::{Request, Response}, config::types::Config, errors::{AuthError, ServerError}, - token::{Claims, Token}, }; -use homedisk_utils::database::{Database, User}; +use homedisk_utils::database::{Database, Error, User}; +use rust_utilities::crypto::jsonwebtoken::{Claims, Token}; pub async fn handle( db: Extension, @@ -16,24 +16,25 @@ pub async fn handle( let response = match db.find_user(&user.username, &user.password).await { Ok(res) => { - let token = Token::new(config.jwt.secret.as_bytes(), Claims::new(res.id)).unwrap(); + let token = Token::new( + config.jwt.secret.as_bytes(), + Claims::new(res.id, config.jwt.expires), + ) + .unwrap(); Response::LoggedIn { access_token: token.encoded, } } - Err(e) => { - use homedisk_utils::database::Error; - match e { - Error::UserNotFound => return Err(ServerError::AuthError(AuthError::UserNotFound)), - _ => { - return Err(ServerError::AuthError(AuthError::UnknowError( - e.to_string(), - ))) - } + Err(err) => match err { + Error::UserNotFound => return Err(ServerError::AuthError(AuthError::UserNotFound)), + _ => { + return Err(ServerError::AuthError(AuthError::UnknowError( + err.to_string(), + ))) } - } + }, }; Ok(Json(response)) diff --git a/server/src/auth/register.rs b/server/src/auth/register.rs index 0e395ce..9af2401 100644 --- a/server/src/auth/register.rs +++ b/server/src/auth/register.rs @@ -3,9 +3,9 @@ use homedisk_types::{ auth::login::{Request, Response}, config::types::Config, errors::{AuthError, ServerError}, - token::{Claims, Token}, }; use homedisk_utils::database::{Database, User}; +use rust_utilities::crypto::jsonwebtoken::{Claims, Token}; pub async fn handle( db: Extension, @@ -16,7 +16,11 @@ pub async fn handle( let response = match db.create_user(&user).await { Ok(_) => { - let token = Token::new(config.jwt.secret.as_bytes(), Claims::new(user.id)).unwrap(); + let token = Token::new( + config.jwt.secret.as_bytes(), + Claims::new(user.id, config.jwt.expires), + ) + .unwrap(); Response::LoggedIn { access_token: token.encoded, diff --git a/types/Cargo.toml b/types/Cargo.toml index 8a6841e..bacfff5 100644 --- a/types/Cargo.toml +++ b/types/Cargo.toml @@ -4,7 +4,6 @@ version = "0.0.0" edition = "2021" [features] -token = ["chrono", "jsonwebtoken"] config = ["toml", "dirs"] [dependencies] @@ -31,11 +30,3 @@ optional = true [dependencies.dirs] version = "4.0.0" optional = true - -# token -[dependencies.chrono] -version = "0.4.19" -optional = true -[dependencies.jsonwebtoken] -version = "8.1.0" -optional = true diff --git a/types/src/config/types.rs b/types/src/config/types.rs index b81bd5c..5d026e4 100644 --- a/types/src/config/types.rs +++ b/types/src/config/types.rs @@ -16,4 +16,5 @@ pub struct ConfigHTTP { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ConfigJWT { pub secret: String, + pub expires: i64, } diff --git a/types/src/lib.rs b/types/src/lib.rs index f0a07d4..cf9c5ed 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -2,6 +2,3 @@ pub mod auth; pub mod config; pub mod errors; pub mod macros; - -#[cfg(feature = "token")] -pub mod token; diff --git a/types/src/token.rs b/types/src/token.rs deleted file mode 100644 index 4c9e807..0000000 --- a/types/src/token.rs +++ /dev/null @@ -1,114 +0,0 @@ -use chrono::{Duration, Utc}; -use jsonwebtoken::{ - decode, encode, errors::Error, Algorithm, DecodingKey, EncodingKey, Header, TokenData, - Validation, -}; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Claims { - pub sub: String, - pub exp: i64, - pub iat: i64, -} - -impl Claims { - /// Generate Json Web Token Claims - /// ``` - /// use homedisk_types::token::Claims; - /// - /// let user_id = "123".to_string(); - /// let claims = Claims::new(user_id); - /// ``` - pub fn new(sub: String) -> Self { - let iat = Utc::now(); - let exp = iat + Duration::hours(24); - - Self { - sub, - iat: iat.timestamp(), - exp: exp.timestamp(), - } - } -} - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub struct Token { - header: Header, - pub claims: Claims, - pub encoded: String, -} - -impl Token { - /// Generate new token - /// ```ignore - /// use homedisk_types::token::{Token, Claims}; - /// - /// let claims = Claims::new("user_id_1234".to_string()); - /// let token = Token::new(secret, claims)?; - /// ``` - pub fn new(key: &[u8], claims: Claims) -> Result { - let header = Header::new(Algorithm::HS256); - let encoded = encode(&header, &claims, &EncodingKey::from_secret(key))?; - - Ok(Self { - header, - claims, - encoded, - }) - } - - /// Validate token - /// ```ignore - /// use homedisk_types::token::{Token, Claims}; - /// - /// let token = Token::new(secret, claims)?; - /// let decoded = Token::decode(secret, token.encoded)?; - /// ``` - pub fn decode(key: &[u8], token: String) -> Result> { - decode::( - &token, - &DecodingKey::from_secret(key), - &Validation::default(), - ) - } -} - -pub type Result = std::result::Result; - -#[cfg(test)] -mod test { - use super::{Claims, Token}; - - fn gen_token(key: &[u8]) -> Token { - Token::new(key, Claims::new("test".to_string())).expect("generate token") - } - - #[test] - fn new_token() { - let key = b"secret"; - gen_token(key); - } - - #[test] - fn decode_token() { - let key = b"secret"; - let token = gen_token(key); - - let decoded = Token::decode(key, token.encoded).unwrap(); - - assert_eq!(decoded.claims, token.claims) - } - - #[test] - fn decode_token_invalid_token() { - let key = b"key"; - let token = gen_token(key); - - let other_key = b"other key"; - - let err = Token::decode(other_key, token.encoded).unwrap_err(); - - assert_eq!(err.to_string(), "InvalidSignature"); - } -} diff --git a/utils/Cargo.toml b/utils/Cargo.toml index 9e5b67d..6fe3e9c 100644 --- a/utils/Cargo.toml +++ b/utils/Cargo.toml @@ -4,8 +4,7 @@ version = "0.0.0" edition = "2021" [features] -full = ["crypto", "database"] -crypto = ["sha-1", "sha2", "hex"] +full = ["database"] database = ["sqlx", "uuid"] [dependencies] @@ -16,17 +15,6 @@ futures-util = "0.3.21" version = "1.0.136" features = ["derive"] -# crypto -[dependencies.sha-1] -version = "0.10.0" -optional = true -[dependencies.sha2] -version = "0.10.2" -optional = true -[dependencies.hex] -version = "0.4.3" -optional = true - # database [dependencies.sqlx] version = "0.5.13" @@ -36,6 +24,9 @@ optional = true version = "1.0.0" features = ["v5"] optional = true +[dependencies.rust_utilities] +version = "0.2.0" +features = ["sha"] [dev-dependencies.tokio] version = "1.17.0" diff --git a/utils/src/crypto/error.rs b/utils/src/crypto/error.rs deleted file mode 100644 index ddfae66..0000000 --- a/utils/src/crypto/error.rs +++ /dev/null @@ -1,16 +0,0 @@ -use std::fmt; - -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum Error { - UnknownAlgorithm(&'static str, String), -} - -pub type Result = std::result::Result; - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Error::UnknownAlgorithm(typ, named) => write!(f, "unknown {} algorithm {}", typ, named), - } - } -} diff --git a/utils/src/crypto/hash.rs b/utils/src/crypto/hash.rs deleted file mode 100644 index c2b829d..0000000 --- a/utils/src/crypto/hash.rs +++ /dev/null @@ -1,120 +0,0 @@ -use sha1::Sha1; -use sha2::{Digest, Sha256, Sha512}; - -use super::{Error, Result}; - -/// create a cryptographic hash from a string (sha1, sha256, sha512) -/// ``` -/// use homedisk_utils::crypto::{CryptographicHash, encode}; -/// -/// let mut sha1 = CryptographicHash::new("SHA-1").unwrap(); -/// sha1.update(b"test sha1 hash"); -/// -/// let hash = encode(sha1.finalize()); -/// -/// assert_eq!(hash, "7726bd9560e1ad4a1a4f056cae5c0c9ea8bacfc2".to_string()) -/// ``` -#[derive(Debug, Clone)] -pub enum CryptographicHash { - Sha1(Sha1), - Sha256(Sha256), - Sha512(Sha512), -} - -impl CryptographicHash { - /// Create a new hasher - pub fn new(algo: &str) -> Result { - match algo { - "SHA-1" | "SHA1" | "Sha1" | "sha1" => Ok(Self::Sha1(Sha1::new())), - "SHA-256" | "SHA256" | "Sha256" | "sha256" => Ok(Self::Sha256(Sha256::new())), - "SHA-512" | "SHA512" | "Sha512" | "sha512" => Ok(Self::Sha512(Sha512::new())), - _ => Err(Error::UnknownAlgorithm("digest", algo.to_string())), - } - } - - /// Set a value for hasher - pub fn update(&mut self, input: &[u8]) { - match self { - Self::Sha1(sha1) => sha1.update(input), - Self::Sha256(sha256) => sha256.update(input), - Self::Sha512(sha512) => sha512.update(input), - } - } - - /// Compute hash - pub fn finalize(&mut self) -> Vec { - match self { - Self::Sha1(sha1) => sha1.finalize_reset().to_vec(), - Self::Sha256(sha256) => sha256.finalize_reset().to_vec(), - Self::Sha512(sha512) => sha512.finalize_reset().to_vec(), - } - } - - /// Streamline the hash calculation to a single function - pub fn hash(algo: &str, input: &[u8]) -> Result> { - let mut hasher = Self::new(algo)?; - - hasher.update(input); - - Ok(hasher.finalize()) - } -} - -#[cfg(test)] -mod tests { - use crate::crypto::{encode, CryptographicHash, Error}; - - #[test] - fn sha1() { - let mut sha1 = CryptographicHash::new("SHA-1").unwrap(); - sha1.update(b"test sha1 hash"); - - let hash = encode(sha1.finalize()); - - assert_eq!(hash, "7726bd9560e1ad4a1a4f056cae5c0c9ea8bacfc2".to_string()) - } - - #[test] - fn sha256() { - let mut sha256 = CryptographicHash::new("SHA-256").unwrap(); - sha256.update(b"test sha256 hash"); - - let hash = encode(sha256.finalize()); - - assert_eq!( - hash, - "eaf6e4198f39ccd63bc3e957d43bf4ef67f12c318c8e3cdc2567a37339902dac".to_string() - ) - } - - #[test] - fn sha512() { - let mut sha512 = CryptographicHash::new("SHA-512").unwrap(); - sha512.update(b"test sha512 hash"); - - let hash = encode(sha512.finalize()); - - assert_eq!( - hash, - "b43b4d7178014c92f55be828d66c9f98211fc67b385f7790a5b4b2fcb89fe1831645b5a4c17f3f7f11d8f34d2800a77a2b8faa5a0fb9d6b8f7befbc29a9ce795".to_string() - ) - } - - #[test] - fn hash_fn() { - let hash = CryptographicHash::hash("SHA-512", b"test sha512 hash").unwrap(); - - assert_eq!( - encode(hash), - "b43b4d7178014c92f55be828d66c9f98211fc67b385f7790a5b4b2fcb89fe1831645b5a4c17f3f7f11d8f34d2800a77a2b8faa5a0fb9d6b8f7befbc29a9ce795".to_string() - ) - } - - #[test] - fn unknown_algorithm() { - let algo = "unknow_algo"; - let err = CryptographicHash::new(algo).unwrap_err(); - - assert_eq!(err, Error::UnknownAlgorithm("digest", algo.to_string())) - } -} diff --git a/utils/src/crypto/mod.rs b/utils/src/crypto/mod.rs deleted file mode 100644 index e8ca832..0000000 --- a/utils/src/crypto/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod error; -mod hash; - -pub use hex::encode; -pub use {error::*, hash::*}; diff --git a/utils/src/database/error.rs b/utils/src/database/error.rs index 572ebca..08e1a47 100644 --- a/utils/src/database/error.rs +++ b/utils/src/database/error.rs @@ -1,21 +1,12 @@ use std::fmt; -use crate::crypto; - #[derive(Debug)] pub enum Error { UserNotFound, - Crypto(crypto::Error), SQLx(sqlx::Error), Io(std::io::Error), } -impl From for Error { - fn from(err: crypto::Error) -> Self { - Error::Crypto(err) - } -} - impl From for Error { fn from(err: sqlx::Error) -> Self { Error::SQLx(err) @@ -32,9 +23,8 @@ impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Error::UserNotFound => write!(f, "user not found"), - Error::Crypto(err) => write!(f, "crypto error: {}", err), Error::SQLx(err) => write!(f, "sqlx error: {}", err), - Error::Io(err) => write!(f, "error: {}", err), + Error::Io(err) => write!(f, "std::io error: {}", err), } } } diff --git a/utils/src/database/user.rs b/utils/src/database/user.rs index 8692ed2..3aeb40e 100644 --- a/utils/src/database/user.rs +++ b/utils/src/database/user.rs @@ -1,8 +1,6 @@ -use hex::encode; +use rust_utilities::crypto::sha::{CryptographicHash, encode, Algorithm}; use uuid::Uuid; -use crate::crypto::CryptographicHash; - #[derive(Debug, sqlx::FromRow)] pub struct User { pub id: String, @@ -11,21 +9,24 @@ pub struct User { } impl User { - /// Create a new User type (note **this doesn't create a new user in the database!**) + /// **Note this doesn't create a new user in the database!** /// - /// This function creates a unique UUID for the user and creates a password hash using SHA-512 + /// This function creates a unique UUID for a user and creates a password hash using SHA-512 + /// and returns in the User type /// ``` /// use homedisk_utils::database::User; /// /// let user = User::new("medzik", "SuperSecretPassword123!"); /// ``` pub fn new(username: &str, password: &str) -> Self { + // change username to lowercase + let username = username.to_lowercase(); + // create user UUID - let sha1_name = CryptographicHash::hash("SHA-1", username.as_bytes()).unwrap(); + let sha1_name = CryptographicHash::hash(Algorithm::SHA1, username.as_bytes()); let id = Uuid::new_v5(&Uuid::NAMESPACE_X500, &sha1_name).to_string(); - let username = username.to_lowercase(); - let password = encode(CryptographicHash::hash("SHA-512", password.as_bytes()).unwrap()); + let password = encode(CryptographicHash::hash(Algorithm::SHA512, password.as_bytes())); Self { id, @@ -48,7 +49,7 @@ mod tests { #[test] fn check_password_is_hashed() { - let password = "Password"; + let password = "password"; let user = User::new("test", password); assert!(user.password != password) diff --git a/utils/src/lib.rs b/utils/src/lib.rs index d352f32..67afb0b 100644 --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -1,5 +1,2 @@ -#[cfg(feature = "crypto")] -pub mod crypto; - #[cfg(feature = "database")] pub mod database;