use std::time::{Duration, SystemTime}; use pasetors::{ claims::{Claims, ClaimsValidationRules}, footer::Footer, keys::SymmetricKey, local, token::UntrustedToken, version4::V4, }; use uuid::Uuid; use crate::models::AppError; static FOURTY_FIVE_DAYS: u64 = 3_888_000; // 60 * 60 * 24 * 45 static ONE_HOUR: u64 = 3_600; pub fn verify_token( key: &SymmetricKey, token: &str, validation_rules: Option, ) -> Result<(), AppError> { let token = UntrustedToken::try_from(token).map_err(|_| AppError::invalid_token())?; let validation_rules = if let Some(validation_rules) = validation_rules { validation_rules } else { ClaimsValidationRules::new() }; let footer = { let mut footer = Footer::new(); footer.key_id(&key.into()); footer }; let _ = local::decrypt( key, &token, &validation_rules, Some(&footer), Some("TODO_ENV_NAME_HERE".as_bytes()), ) .map_err(|_| AppError::invalid_token())?; Ok(()) } pub fn generate_access_token(key: &SymmetricKey, user_id: i32) -> (String, SystemTime) { generate_token( key, user_id, Some(Duration::from_secs(FOURTY_FIVE_DAYS)), None, ) } pub fn generate_auth_token(key: &SymmetricKey, user_id: i32) -> (String, SystemTime) { generate_token(key, user_id, None, None) } pub fn generate_token( key: &SymmetricKey, user_id: i32, duration: Option, audience: Option<&str>, ) -> (String, SystemTime) { let now = SystemTime::now(); let expiration = if let Some(duration) = duration { duration } else { Duration::from_secs(ONE_HOUR) }; let token = Claims::new_expires_in(&expiration) .and_then(|mut claims| { claims .token_identifier(Uuid::now_v7().to_string().as_str()) .map(|_| claims) }) .and_then(|mut claims| claims.issuer("auth-test").map(|_| claims)) .and_then(|mut claims| claims.subject(user_id.to_string().as_str()).map(|_| claims)) .and_then(|mut claims| { if let Some(audience) = audience { claims.audience(audience).map(|_| claims) } else { Ok(claims) } }) .and_then(|claims| { let footer = { let mut footer = Footer::new(); footer.key_id(&key.into()); footer }; local::encrypt( &key, &claims, Some(&footer), Some("TODO_ENV_NAME_HERE".as_bytes()), ) }) .unwrap(); (token, now + expiration) } #[cfg(test)] mod tests { use base64::prelude::*; use pasetors::paserk::Id; use serde_json::Value; use super::*; #[test] fn test_does_verify_token_audience_claim() { let zero = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; let key = BASE64_STANDARD .decode(zero) .map_err(|_| ()) .and_then(|bytes| SymmetricKey::::from(bytes.as_slice()).map_err(|_| ())) .unwrap(); let token = generate_token(&key, 1, Some(Duration::from_secs(60)), Some("testing")).0; let footer = { let mut footer = Footer::new(); footer.key_id(&Id::from(&key)); footer }; let validation_rules = { let mut rules = ClaimsValidationRules::new(); rules.validate_audience_with("testing"); rules }; let trusted_token = local::decrypt( &key, &UntrustedToken::try_from(token.as_str()).unwrap(), &validation_rules, Some(&footer), Some("TODO_ENV_NAME_HERE".as_bytes()), ); assert!(trusted_token.is_ok()); let trusted_token = trusted_token.unwrap(); let claims = trusted_token.payload_claims(); assert!(claims.is_some()); let claims = claims.unwrap(); assert_eq!( claims.get_claim("aud"), Some(&Value::String("testing".to_string())) ); } }