chore: use crate rust_utilities
This commit is contained in:
parent
dc28e67b5c
commit
bf5422b47f
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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 {
|
||||
Err(err) => match err {
|
||||
Error::UserNotFound => return Err(ServerError::AuthError(AuthError::UserNotFound)),
|
||||
_ => {
|
||||
return Err(ServerError::AuthError(AuthError::UnknowError(
|
||||
e.to_string(),
|
||||
err.to_string(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Ok(Json(response))
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -16,4 +16,5 @@ pub struct ConfigHTTP {
|
|||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ConfigJWT {
|
||||
pub secret: String,
|
||||
pub expires: i64,
|
||||
}
|
||||
|
|
|
@ -2,6 +2,3 @@ pub mod auth;
|
|||
pub mod config;
|
||||
pub mod errors;
|
||||
pub mod macros;
|
||||
|
||||
#[cfg(feature = "token")]
|
||||
pub mod token;
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()))
|
||||
}
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
mod error;
|
||||
mod hash;
|
||||
|
||||
pub use hex::encode;
|
||||
pub use {error::*, hash::*};
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,5 +1,2 @@
|
|||
#[cfg(feature = "crypto")]
|
||||
pub mod crypto;
|
||||
|
||||
#[cfg(feature = "database")]
|
||||
pub mod database;
|
||||
|
|
Loading…
Reference in New Issue