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}