2024-08-06 11:08:15 -04:00
|
|
|
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;
|
2024-08-06 11:08:15 -04:00
|
|
|
|
|
|
|
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()))
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|