chore: use crate rust_utilities

This commit is contained in:
MedzikUser 2022-04-23 12:52:56 +02:00
parent dc28e67b5c
commit bf5422b47f
No known key found for this signature in database
GPG Key ID: A5FAC1E185C112DB
16 changed files with 62 additions and 326 deletions

21
Cargo.lock generated
View File

@ -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"

View File

@ -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

View File

@ -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"]

View File

@ -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<Database>,
@ -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))

View File

@ -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<Database>,
@ -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,

View File

@ -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

View File

@ -16,4 +16,5 @@ pub struct ConfigHTTP {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConfigJWT {
pub secret: String,
pub expires: i64,
}

View File

@ -2,6 +2,3 @@ pub mod auth;
pub mod config;
pub mod errors;
pub mod macros;
#[cfg(feature = "token")]
pub mod token;

View File

@ -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<Self> {
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<TokenData<Claims>> {
decode::<Claims>(
&token,
&DecodingKey::from_secret(key),
&Validation::default(),
)
}
}
pub type Result<T> = std::result::Result<T, Error>;
#[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");
}
}

View File

@ -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"

View File

@ -1,16 +0,0 @@
use std::fmt;
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Error {
UnknownAlgorithm(&'static str, String),
}
pub type Result<T> = std::result::Result<T, Error>;
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),
}
}
}

View File

@ -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<Self> {
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<u8> {
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<Vec<u8>> {
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()))
}
}

View File

@ -1,5 +0,0 @@
mod error;
mod hash;
pub use hex::encode;
pub use {error::*, hash::*};

View File

@ -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<crypto::Error> for Error {
fn from(err: crypto::Error) -> Self {
Error::Crypto(err)
}
}
impl From<sqlx::Error> 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),
}
}
}

View File

@ -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)

View File

@ -1,5 +1,2 @@
#[cfg(feature = "crypto")]
pub mod crypto;
#[cfg(feature = "database")]
pub mod database;