Updating ui
This commit is contained in:
123
service/src/auth/token.rs
Normal file
123
service/src/auth/token.rs
Normal file
@@ -0,0 +1,123 @@
|
||||
use std::env;
|
||||
|
||||
use argon2::{password_hash::{rand_core::OsRng, SaltString}, Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
|
||||
use jsonwebtoken::{DecodingKey, EncodingKey, Header, encode, decode, Validation, Algorithm};
|
||||
use rand::prelude::*;
|
||||
use rand_chacha::ChaCha20Rng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use siren::ServiceError;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct TokenClaims {
|
||||
sub: String, // Subject (User)
|
||||
id: String, // Access Token ID
|
||||
iss: String, // Issuer (Service)
|
||||
exp: i64, // Expiration time
|
||||
iat: i64, // Issued At
|
||||
nbf: i64 // Not Before
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct AccessToken {
|
||||
pub token: Option<String>, // Access Token
|
||||
pub id: String, // Access Token ID
|
||||
pub email: String, // Subject (User)
|
||||
pub expiration: Option<i64> // Expiration time
|
||||
}
|
||||
|
||||
impl AccessToken {
|
||||
fn new(email: &str) -> Result<Self, ServiceError> {
|
||||
let ttl = env::var("ACCESS_TOKEN_MAXAGE")
|
||||
.expect("ACCESS_TOKEN_MAXAGE must be set")
|
||||
.parse::<i64>()
|
||||
.expect("ACCESS_TOKEN_MAXAGE must be an integer");
|
||||
let keys_dir = env::var("KEYS_DIR_PATH")?;
|
||||
let private_key = std::fs::read_to_string(format!("{}/private_key.pem", keys_dir))?;
|
||||
let now = chrono::Utc::now();
|
||||
let mut token_details = Self {
|
||||
token: None,
|
||||
id: csprng_128bit(),
|
||||
email: email.to_string(),
|
||||
expiration: Some((now + chrono::Duration::minutes(ttl)).timestamp())
|
||||
};
|
||||
let claims = TokenClaims {
|
||||
sub: token_details.email.clone(),
|
||||
iss: "siren".to_string(),
|
||||
id: token_details.id.to_string(),
|
||||
exp: token_details.expiration.unwrap(),
|
||||
iat: now.timestamp(),
|
||||
nbf: now.timestamp()
|
||||
};
|
||||
let header = Header::new(Algorithm::RS256);
|
||||
let key = EncodingKey::from_rsa_pem(private_key.as_bytes())?;
|
||||
let token = encode(&header, &claims, &key)?;
|
||||
token_details.token = Some(token);
|
||||
Ok(token_details)
|
||||
}
|
||||
|
||||
pub fn decode(token: &str, public_key: &str) -> Result<Self, ServiceError> {
|
||||
let key = DecodingKey::from_rsa_pem(public_key.as_bytes())?;
|
||||
let validation = Validation::new(Algorithm::RS256);
|
||||
let decoded = decode::<TokenClaims>(token, &key, &validation)?;
|
||||
let email = decoded.claims.sub;
|
||||
let id = decoded.claims.id;
|
||||
Ok(Self { token: None, id, email, expiration: None })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct RefreshToken {
|
||||
pub id: String,
|
||||
pub email: String,
|
||||
pub ip_address: String,
|
||||
pub tokens: Vec<String>,
|
||||
pub expiration: i64,
|
||||
}
|
||||
|
||||
impl RefreshToken {
|
||||
pub fn new(email: &str, ip_address: &str) -> Self {
|
||||
let ttl = env::var("REFRESH_TOKEN_MAXAGE")
|
||||
.expect("REFRESH_TOKEN_MAXAGE must be set")
|
||||
.parse::<i64>()
|
||||
.expect("REFRESH_TOKEN_MAXAGE must be an integer");
|
||||
let now = chrono::Utc::now();
|
||||
Self {
|
||||
id: csprng_128bit(),
|
||||
email: email.to_string(),
|
||||
ip_address: hash(&ip_address).unwrap(),
|
||||
tokens: vec![],
|
||||
expiration: (now + chrono::Duration::minutes(ttl)).timestamp()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_access_token(&mut self) -> Result<AccessToken, ServiceError> {
|
||||
let access_token = AccessToken::new(&self.email)?;
|
||||
self.tokens.push(access_token.id.clone());
|
||||
Ok(access_token)
|
||||
}
|
||||
}
|
||||
|
||||
fn csprng_128bit() -> String {
|
||||
// Generate a CSPRNG 128-bit (16 byte) ID using alphanumeric characters (a-z, A-Z, 0-9)
|
||||
let rng = ChaCha20Rng::from_entropy();
|
||||
rng.sample_iter(rand::distributions::Alphanumeric).take(16).map(char::from).collect()
|
||||
}
|
||||
|
||||
pub fn hash(str: &str) -> Result<String, ServiceError> {
|
||||
let salt = SaltString::generate(&mut OsRng);
|
||||
let bytes = str.as_bytes();
|
||||
let hash = Argon2::default().hash_password(bytes, &salt)?.to_string();
|
||||
Ok(hash)
|
||||
}
|
||||
|
||||
pub fn verify_hash(str: &str, hash: &str) -> bool {
|
||||
let bytes = str.as_bytes();
|
||||
let parsed_hash = match PasswordHash::new(hash) {
|
||||
Ok(h) => h,
|
||||
Err(_) => return false
|
||||
};
|
||||
match Argon2::default().verify_password(bytes, &parsed_hash) {
|
||||
Ok(_) => true,
|
||||
Err(_) => false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user