Skip to main content

cataclysm_jwt/jwt_session/
hs256_session.rs

1use crate::{Error, error::{JWTError,ConstructionError}, sign_algorithms::HS256, jwt_session::JWTSession, JWT};
2
3use chrono::{DateTime, Utc};
4use std::collections::HashMap;
5use cataclysm::{session::{SessionCreator, Session}, http::Request};
6
7#[derive(Clone)]
8/// Implementation of HS256 session, or symmetric session (the most common at least)
9pub struct JWTHS256Session {
10    pub aud: String,
11    pub iss: String,
12    pub verification_key: HS256,
13    #[cfg(feature = "delta-start")]
14    pub delta_start: Option<i64>,
15}
16
17impl JWTHS256Session {
18
19    /// Simple builder function
20    pub fn builder() -> JWTHS256Builder {
21        JWTHS256Builder::default()
22    }
23
24}
25
26impl SessionCreator for JWTHS256Session {
27
28    fn apply(&self, _values: &HashMap<String, String>, res: cataclysm::http::Response) -> cataclysm::http::Response {
29        res
30    }
31
32    fn create(&self, req: &cataclysm::http::Request) -> Result<cataclysm::session::Session, cataclysm::Error> {
33        match self.build_from_req(req) {
34            Ok(payload) => {
35                return Ok(Session::new_with_values(self.clone(),payload))
36            },
37            Err(_) => {
38                return Err(cataclysm::Error::Custom(format!("Unable to create session!!")));
39            }
40        }
41    }
42
43}
44
45impl JWTSession for JWTHS256Session {
46
47    fn build_from_req(&self, req: &Request) -> Result<HashMap<String,String>, Error> {
48        
49        let jwt = Self::obtain_token_from_req(req)?;
50
51        self.initial_validation(&jwt)?;
52
53        return Ok(jwt.payload)
54
55
56    }
57
58    fn initial_validation(&self, jwt: &JWT) -> Result<(),Error> {
59
60        // Check the algorithm on jwt is the same as the one in the key
61        match jwt.header.get("alg") {
62            Some(a) => {
63                if a.to_lowercase().as_str() != self.verification_key.to_string() {
64                    return Err(Error::JWT(JWTError::WrongAlgorithm));
65                }
66            },
67            None => {
68                return Err(Error::JWT(JWTError::NoAlgorithm));
69            }
70        };
71
72        #[cfg(not(feature = "lax-security"))]
73        {
74            // Check the audience
75            match jwt.payload.get("aud") {
76                Some(a) => {
77                    if a.as_str() != &self.aud {
78                        return Err(Error::JWT(JWTError::WrongAudience));
79                    }
80                },
81                None => {
82                    return Err(Error::JWT(JWTError::NoAudience))
83                }
84            }
85
86            // Check the issuer
87            match jwt.payload.get("iss") {
88                Some(i) => {
89                    if i.as_str() != &self.iss {
90                        return Err(Error::JWT(JWTError::WrongIss));
91                    }
92                },
93                None => {
94                    return Err(Error::JWT(JWTError::NoIss))
95                }
96            }
97
98            // Check the expiration time
99            match jwt.payload.get("exp") {
100                Some(e) => {
101
102                    let num_e = str::parse::<i64>(e)?;
103                    let date_utc = DateTime::from_timestamp(num_e,0).ok_or(Error::ParseTimestamp)?;
104                    let now = Utc::now();
105                    
106                    if date_utc < now {
107                        return Err(Error::JWT(JWTError::Expired));
108                    }
109
110                },
111                None => {
112                    return Err(Error::JWT(JWTError::NoExp))
113                }
114            }
115            
116            // Check the iat
117            match jwt.payload.get("iat") {
118                Some(ia) => {
119                    let num_ia = str::parse::<i64>(ia)?;
120                    let now = Utc::now();
121                    
122                    #[cfg(not(feature = "delta-start"))] {
123                        let date_utc = DateTime::from_timestamp(num_ia,0).ok_or(Error::ParseTimestamp)?;
124                        if date_utc > now {
125                            return Err(Error::JWT(JWTError::ToBeValid));
126                        }
127                    }
128                    
129                    #[cfg(feature = "delta-start")] {
130                        let delta = match self.delta_start {
131                            Some(d) => d,
132                            None => 0
133                        };
134                        let date_utc = DateTime::from_timestamp(num_ia - delta,0).ok_or(Error::ParseTimestamp)?;
135                        if date_utc > now {
136                            return Err(Error::JWT(JWTError::Expired));
137                        }
138                    }
139                },
140                None => {
141                    return Err(Error::JWT(JWTError::NoIat))
142                }
143            }
144
145            match jwt.payload.get("nbf") {
146                Some(nb) => {
147                    let num_nb = str::parse::<i64>(nb)?;
148                    let now = Utc::now();
149                    
150                    #[cfg(not(feature = "delta-start"))] {
151                        let date_utc = DateTime::from_timestamp(num_nb,0).ok_or(Error::ParseTimestamp)?;
152                        if date_utc > now {
153                            return Err(Error::JWT(JWTError::ToBeValid));
154                        }
155                    }
156                    
157                    #[cfg(feature = "delta-start")] {
158                        let delta = match self.delta_start {
159                            Some(d) => d,
160                            None => 0
161                        };
162                        let date_utc = DateTime::from_timestamp(num_nb - delta,0).ok_or(Error::ParseTimestamp)?;
163                        if date_utc > now {
164                            return Err(Error::JWT(JWTError::Expired));
165                        }
166                    }
167                },
168                None => {
169                    return Err(Error::JWT(JWTError::NoNbf))
170                }
171            }
172            
173        }
174
175        self.verification_key.verify_jwt(&jwt.raw_jwt)
176
177    }
178
179}
180
181#[derive(Default)]
182/// Simple builder for HS256 session
183pub struct JWTHS256Builder {
184    aud: Option<String>,
185    iss: Option<String>,
186    verification_key: Option<HS256>,
187    #[cfg(feature = "delta-start")]
188    delta_start: Option<i64>,
189}
190
191impl JWTHS256Builder {
192    
193    /// Get audience
194    pub fn aud<A: AsRef<str>>(self, aud: A) -> Self {
195        Self {
196            aud: Some(aud.as_ref().to_string()),
197            ..self
198        }
199    }
200
201    /// Get issuer
202    pub fn iss<A: AsRef<str>>(self, iss: A) -> Self {
203        Self {
204            iss: Some(iss.as_ref().to_string()),
205            ..self
206        }
207    }
208
209    /// Create HS256 key from shared secret
210    pub fn add_from_secret<A: AsRef<str>>(self, secret: A) -> Self {
211        
212        let verification_key = HS256::new(secret);
213
214        Self {
215            verification_key: Some(verification_key),
216            ..self
217        }
218
219    }
220
221    /// Adds a time extension to verify nbf and iat claims.
222    /// If the time extension is called 'delta', then the token is valid since (iat - delta) and (nbf - delta).
223    /// Even if the feature is enabled, when no time window is passed, the time extension will not be enabled.
224    #[cfg(feature = "delta-expiration")]
225    pub fn delta_start(self, delta_exipration: i64) -> Self {
226        
227        Self {
228            delta_start: Some(delta_start),
229            ..self
230        }
231
232    }
233
234    /// Simple builder
235    pub fn build(self) -> Result<JWTHS256Session, Error> {
236        
237        let aud = match self.aud {
238            Some(a) => a,
239            None => {
240                return Err(Error::Construction(ConstructionError::Aud));
241            }
242        };
243
244        let iss = match self.iss {
245            Some(i) => i,
246            None => {
247                return Err(Error::Construction(ConstructionError::Iss));
248            }
249        };
250
251        let verification_key = match self.verification_key {
252            Some(k) => k,
253            None => {
254                return Err(Error::Construction(ConstructionError::Keys))
255            }
256        };
257        
258        Ok(JWTHS256Session {
259            aud,
260            iss,
261            verification_key,
262            #[cfg(feature = "delta-start")]
263            delta_start: self.delta_start
264        })
265    }
266}