actix_jc/
lib.rs

1use std::borrow::Cow;
2use std::marker::PhantomData;
3
4use actix_web::cookie::CookieBuilder;
5use actix_web::HttpRequest;
6use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
7use actix_web::cookie::{Cookie, time::Duration};
8use serde::{Serialize, Deserialize};
9
10pub struct ActixJwtCookie<T> {
11    pub name: Cow<'static, str>,
12    pub exist: bool,
13    pub expiration: AuthExpiration,
14    pub jwt_key: Cow<'static, str>,
15    _marker: PhantomData<T>,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
19struct Claims<T> {
20    model: T,
21    exp: usize,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub enum AuthExpiration {
26    Permanent,
27    Time(Duration),
28}
29
30impl<T> ActixJwtCookie<T> where T: Serialize + for<'de> Deserialize<'de> + 'static {
31    pub fn new() -> Self {
32       Self::default() 
33    }
34
35    pub fn cookie_name(mut self, cookie_name: &'static str) -> Self {
36        self.name = Cow::Borrowed(cookie_name);
37        self
38    }
39
40    pub fn expiration(mut self, seconds: i64) -> Self {
41        self.expiration = AuthExpiration::Time(Duration::seconds(seconds));
42        self
43    }
44
45    pub fn permanent(mut self) -> Self {
46        self.expiration = AuthExpiration::Permanent;
47        self
48    }
49
50    pub fn jwt_key(mut self, jwt_key: &'static str) -> Self {
51        self.jwt_key = Cow::Borrowed(jwt_key);
52        self
53    }
54
55    fn create_jwt(&self, model: T) -> String {
56        let claims = Claims {
57            model,
58            exp: 170000000000000,
59        };
60
61        match encode(&Header::default(), &claims, &EncodingKey::from_secret(self.jwt_key.as_bytes())) {
62            Ok(t) => t,
63            Err(err) => {
64                println!("Error occurred when encoding jwt: {}", err);
65                panic!()
66            }
67        }
68    }
69
70    fn verify_jwt_and_return_value(&self, jwt_token: &str) -> Result<T, jsonwebtoken::errors::Error> {
71        let token = match decode::<Claims<T>>(
72            jwt_token,
73            &DecodingKey::from_secret(self.jwt_key.as_bytes()),
74            &Validation::default()
75        ) {
76            Ok(t) => t,
77            Err(err) => {
78                println!("Error occurred when decoding jwt: {}", err);
79                return Err(err);
80            },
81        };
82
83        Ok(token.claims.model)
84    }
85
86    pub fn exist(&self, req: HttpRequest) -> Option<T> {
87        match req.cookie(&self.name) {
88            Some(cookie) => {
89                match self.verify_jwt_and_return_value(cookie.value()) {
90                    Ok(data) => Some(data),
91                    Err(error) => {
92                        println!("That error occured when we verify the jwt on .check() method: {}", error);
93
94                        None
95                    }
96                }
97            },
98            None => None
99        } 
100    }
101
102    pub fn create(&self, data: T) -> CookieBuilder<'_> {
103        let jwt = self.create_jwt(data);
104
105        match self.expiration {
106            AuthExpiration::Time(time) => Cookie::build(self.name.clone(), jwt).max_age(time),
107            AuthExpiration::Permanent => Cookie::build(self.name.clone(), jwt).permanent()
108        }
109    }
110}
111
112impl<T> Default for ActixJwtCookie<T> where T: Serialize + for<'de> Deserialize<'de> + 'static {
113    fn default() -> Self {
114        Self {
115            name: Cow::Borrowed("actix-jwt-cookie"),
116            exist: false,
117            expiration: AuthExpiration::Time(Duration::seconds(7200)),
118            jwt_key: Cow::Borrowed("stand with palestine!"),
119            _marker: PhantomData,
120        }
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn is_valid_actix_cookie() {
130        let configure_builder = ActixJwtCookie::new().cookie_name("my_cookie").jwt_key("asfasdfas").permanent();
131
132        let build = configure_builder.create(12).finish();
133
134        let create_cookie = CookieBuilder::new("my_cookie", "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJtb2RlbCI6MTIsImV4cCI6MTcwMDAwMDAwMDAwMDAwfQ.MLimFgGj1U8Ds7_QnS_WP3fWwQB3bfkRClAOlU3A1cU").permanent().finish();
135
136        assert_eq!(build.name(), create_cookie.name());
137        assert_eq!(build.value(), create_cookie.value());
138        assert!(build.expires().is_some());
139        assert!(build.max_age().is_some());
140    }
141
142    #[test]
143    fn test_is_decodes_correct(){
144        let decoded_value = match decode::<Claims<i32>>(
145            "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJtb2RlbCI6MTIsImV4cCI6MTcwMDAwMDAwMDAwMDAwfQ.MLimFgGj1U8Ds7_QnS_WP3fWwQB3bfkRClAOlU3A1cU",
146            &DecodingKey::from_secret(Cow::Borrowed("asfasdfas").as_bytes()),
147            &Validation::default()
148        ) {
149            Ok(t) => t.claims.model,
150            Err(err) => {
151                println!("Error occurred when decoding jwt: {}", err);
152                
153                panic!("{}", err)
154            },
155        };
156
157        assert_eq!(decoded_value, 12)
158    }
159}