1use crate::key_management::KeyInfo;
5use crate::shim::crypto::SignatureType;
6use crate::utils::misc::env::is_env_truthy;
7use chrono::{Duration, Utc};
8use jsonwebtoken::{DecodingKey, EncodingKey, Header, decode, encode, errors::Result as JWTResult};
9use rand::Rng;
10use serde::{Deserialize, Serialize};
11
12pub const JWT_IDENTIFIER: &str = "auth-jwt-private";
14pub const ADMIN: &[&str] = &["read", "write", "sign", "admin"];
16pub const SIGN: &[&str] = &["read", "write", "sign"];
18pub const WRITE: &[&str] = &["read", "write"];
20pub const READ: &[&str] = &["read"];
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25struct Claims {
26 #[serde(rename = "Allow")]
27 allow: Vec<String>,
28 #[serde(default)]
30 exp: Option<usize>,
31}
32
33pub fn create_token(perms: Vec<String>, key: &[u8], token_exp: Duration) -> JWTResult<String> {
35 let exp_time = Utc::now() + token_exp;
36 let payload = Claims {
37 allow: perms,
38 exp: Some(exp_time.timestamp() as usize),
39 };
40 encode(&Header::default(), &payload, &EncodingKey::from_secret(key))
41}
42
43pub fn verify_token(token: &str, key: &[u8]) -> JWTResult<Vec<String>> {
45 let mut validation = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::default());
46 if is_env_truthy("FOREST_JWT_DISABLE_EXP_VALIDATION") {
47 let mut claims = validation.required_spec_claims.clone();
48 claims.remove("exp");
49 let buff: Vec<_> = claims.iter().collect();
50 validation.set_required_spec_claims(&buff);
51 validation.validate_exp = false;
52 }
53 let token = decode::<Claims>(token, &DecodingKey::from_secret(key), &validation)?;
54 Ok(token.claims.allow)
55}
56
57pub fn generate_priv_key() -> KeyInfo {
58 let priv_key = crate::utils::rand::forest_os_rng().r#gen::<[u8; 32]>();
59 KeyInfo::new(SignatureType::Bls, priv_key.to_vec())
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67 use serial_test::serial;
68
69 fn create_token_without_exp(perms: Vec<String>, key: &[u8]) -> JWTResult<String> {
71 let payload = Claims {
72 allow: perms,
73 exp: None,
74 };
75 encode(&Header::default(), &payload, &EncodingKey::from_secret(key))
76 }
77
78 #[test]
79 #[serial]
80 fn create_and_verify_token() {
81 let perms_expected = vec![
82 "Ph'nglui mglw'nafh Cthulhu".to_owned(),
83 "R'lyeh wgah'nagl fhtagn".to_owned(),
84 ];
85 let key = generate_priv_key();
86
87 let token = create_token(
89 perms_expected.clone(),
90 key.private_key(),
91 Duration::try_hours(1).expect("Infallible"),
92 )
93 .unwrap();
94 let perms = verify_token(&token, key.private_key()).unwrap();
95 assert_eq!(perms_expected, perms);
96
97 let token = create_token(
99 perms_expected.clone(),
100 key.private_key(),
101 -Duration::try_hours(1).expect("Infallible"),
102 )
103 .unwrap();
104 assert!(verify_token(&token, key.private_key()).is_err());
105
106 let token = create_token(
109 perms_expected.clone(),
110 key.private_key(),
111 -Duration::try_seconds(10).expect("Infallible"),
112 )
113 .unwrap();
114 let perms = verify_token(&token, key.private_key()).unwrap();
115 assert_eq!(perms_expected, perms);
116 }
117
118 #[test]
119 #[serial]
120 fn create_and_verify_token_without_exp() {
121 let perms_expected = vec![
122 "Ia! Ia! Cthulhu fhtagn".to_owned(),
123 "Zin-Mi-Yak, dread lord of the deep".to_owned(),
124 ];
125 let key = generate_priv_key();
126
127 unsafe {
129 std::env::set_var("FOREST_JWT_DISABLE_EXP_VALIDATION", "1");
130 }
131
132 let token = create_token_without_exp(perms_expected.clone(), key.private_key()).unwrap();
134 let perms = verify_token(&token, key.private_key()).unwrap();
135 assert_eq!(perms_expected, perms);
136
137 let token = create_token(
139 perms_expected.clone(),
140 key.private_key(),
141 -Duration::try_hours(1).expect("Infallible"),
142 )
143 .unwrap();
144 let perms = verify_token(&token, key.private_key()).unwrap();
145 assert_eq!(perms_expected, perms);
146
147 unsafe {
148 std::env::remove_var("FOREST_JWT_DISABLE_EXP_VALIDATION");
149 }
150 }
151}