Files
siren/service/src/auth/token.rs
Benjamin Sherriff 57286bb0e7 Updating ui
2024-01-30 20:09:51 -05:00

123 lines
3.9 KiB
Rust

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