debt-pirate/api/src/services/auth_token.rs

162 lines
4.2 KiB
Rust
Raw Normal View History

use std::time::{Duration, SystemTime};
use pasetors::{
claims::{Claims, ClaimsValidationRules},
footer::Footer,
keys::SymmetricKey,
local,
token::UntrustedToken,
version4::V4,
};
use uuid::Uuid;
2024-08-24 13:22:51 -04:00
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<V4>,
token: &str,
validation_rules: Option<ClaimsValidationRules>,
) -> 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<V4>, 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<V4>, user_id: i32) -> (String, SystemTime) {
generate_token(key, user_id, None, None)
}
pub fn generate_token(
key: &SymmetricKey<V4>,
user_id: i32,
duration: Option<Duration>,
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::<V4>::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()))
);
}
}