use crate::key_management::KeyInfo;
use crate::shim::crypto::SignatureType;
use chrono::{Duration, Utc};
use jsonwebtoken::{DecodingKey, EncodingKey, Header, decode, encode, errors::Result as JWTResult};
use rand::Rng;
use serde::{Deserialize, Serialize};
pub const JWT_IDENTIFIER: &str = "auth-jwt-private";
pub const ADMIN: &[&str] = &["read", "write", "sign", "admin"];
pub const SIGN: &[&str] = &["read", "write", "sign"];
pub const WRITE: &[&str] = &["read", "write"];
pub const READ: &[&str] = &["read"];
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Claims {
#[serde(rename = "Allow")]
allow: Vec<String>,
#[serde(default)]
exp: Option<usize>,
}
pub fn create_token(perms: Vec<String>, key: &[u8], token_exp: Duration) -> JWTResult<String> {
let exp_time = Utc::now() + token_exp;
let payload = Claims {
allow: perms,
exp: Some(exp_time.timestamp() as usize),
};
encode(&Header::default(), &payload, &EncodingKey::from_secret(key))
}
pub fn verify_token(token: &str, key: &[u8]) -> JWTResult<Vec<String>> {
crate::def_is_env_truthy!(disable_exp_validation, "FOREST_JWT_DISABLE_EXP_VALIDATION");
let mut validation = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::default());
if disable_exp_validation() {
let mut claims = validation.required_spec_claims.clone();
claims.remove("exp");
let buff: Vec<_> = claims.iter().collect();
validation.set_required_spec_claims(&buff);
validation.validate_exp = false;
}
let token = decode::<Claims>(token, &DecodingKey::from_secret(key), &validation)?;
Ok(token.claims.allow)
}
pub fn generate_priv_key() -> KeyInfo {
let priv_key = crate::utils::rand::forest_os_rng().r#gen::<[u8; 32]>();
KeyInfo::new(SignatureType::Bls, priv_key.to_vec())
}
#[cfg(test)]
mod tests {
use super::*;
use serial_test::serial;
fn create_token_without_exp(perms: Vec<String>, key: &[u8]) -> JWTResult<String> {
let payload = Claims {
allow: perms,
exp: None,
};
encode(&Header::default(), &payload, &EncodingKey::from_secret(key))
}
#[test]
#[serial]
fn create_and_verify_token() {
let perms_expected = vec![
"Ph'nglui mglw'nafh Cthulhu".to_owned(),
"R'lyeh wgah'nagl fhtagn".to_owned(),
];
let key = generate_priv_key();
let token = create_token(
perms_expected.clone(),
key.private_key(),
Duration::hours(1),
)
.unwrap();
let perms = verify_token(&token, key.private_key()).unwrap();
assert_eq!(perms_expected, perms);
let token = create_token(
perms_expected.clone(),
key.private_key(),
-Duration::hours(1),
)
.unwrap();
assert!(verify_token(&token, key.private_key()).is_err());
let token = create_token(
perms_expected.clone(),
key.private_key(),
-Duration::seconds(10),
)
.unwrap();
let perms = verify_token(&token, key.private_key()).unwrap();
assert_eq!(perms_expected, perms);
}
#[test]
#[serial]
fn create_and_verify_token_without_exp() {
let perms_expected = vec![
"Ia! Ia! Cthulhu fhtagn".to_owned(),
"Zin-Mi-Yak, dread lord of the deep".to_owned(),
];
let key = generate_priv_key();
unsafe {
std::env::set_var("FOREST_JWT_DISABLE_EXP_VALIDATION", "1");
}
let token = create_token_without_exp(perms_expected.clone(), key.private_key()).unwrap();
let perms = verify_token(&token, key.private_key()).unwrap();
assert_eq!(perms_expected, perms);
let token = create_token(
perms_expected.clone(),
key.private_key(),
-Duration::hours(1),
)
.unwrap();
let perms = verify_token(&token, key.private_key()).unwrap();
assert_eq!(perms_expected, perms);
unsafe {
std::env::remove_var("FOREST_JWT_DISABLE_EXP_VALIDATION");
}
}
}