1use std::sync::Arc;
2
3use crate::types::CookieMap;
4pub use crate::types::HttpResonse;
5use async_trait::async_trait;
6use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
7use hmac::Mac;
8use hyper::header;
9use redis::AsyncCommands;
10
11use serde_json::{json, Value};
12use sha2::Sha256;
13use std::any;
14use uuid::Uuid;
15
16#[derive(Debug)]
17pub struct RedisSession {
18 inner: Value,
19 sid: String,
20}
21
22fn build_rkey(v: &Vec<&str>) -> String {
23 v.join(":")
24}
25
26fn build_session_key(session_id: &str) -> String {
27 build_rkey(&vec![SESSION_PREFIX, session_id])
28}
29
30static NO_SUCH_USER: &'static str = "Can't find this session";
31static NO_COOKIE: &'static str = "No cookie";
32static NO_SESSION_ID: &'static str = "No session id";
33static SESSION_PREFIX: &'static str = "session";
34
35#[async_trait]
36impl Session for RedisSession {
37 fn get(&self, key: &str) -> anyhow::Result<Option<Value>> {
38 Ok(self.inner.get(key).cloned())
39 }
40
41 fn set(&mut self, key: &str, value: Value) -> anyhow::Result<()> {
42 Ok(self.inner[key] = value)
43 }
44
45 fn value(&self) -> &Value {
46 &self.inner
47 }
48 fn as_any(&self) -> &dyn any::Any {
49 self
50 }
51}
52
53#[async_trait]
54pub trait Session: Send + Sync + 'static + any::Any {
55 fn get(&self, key: &str) -> anyhow::Result<Option<Value>>;
56 fn set(&mut self, key: &str, value: Value) -> anyhow::Result<()>;
57 fn value(&self) -> &Value;
58 fn as_any(&self) -> &dyn any::Any;
59}
60
61#[async_trait]
62pub trait SessionProvider: Send + Sync + 'static {
63 async fn open_session(
66 &self,
67 cookie_map: &CookieMap,
68 ) -> anyhow::Result<Option<Box<dyn Session>>>;
69 async fn save_session(
70 &self,
71 session: &Box<dyn Session>,
72 res: &mut HttpResonse,
73 ) -> anyhow::Result<()>;
74}
75
76pub struct RedisSessionProvider {
77 redis_cli: Arc<redis::Client>, }
79
80impl RedisSessionProvider {
81 pub fn new(redis_cli: Arc<redis::Client>) -> Self {
82 RedisSessionProvider { redis_cli }
83 }
84
85 fn new_session(&self) -> Box<dyn Session> {
86 let sid = Uuid::new_v4().to_string();
87 let session = RedisSession {
88 inner: json!({}),
89 sid,
90 };
91 Box::new(session)
92 }
93}
94
95#[async_trait]
96impl SessionProvider for RedisSessionProvider {
97 async fn open_session(
98 &self,
99 cookie_map: &CookieMap,
100 ) -> anyhow::Result<Option<Box<dyn Session>>> {
101 let sid = match cookie_map.get("session_id") {
102 Some(v) => v,
103 None => return Ok(Some(self.new_session())),
104 };
105 let mut conn = self.redis_cli.get_async_connection().await?;
106 let ov: Option<String> = conn.get(build_session_key(&sid)).await?;
107 let serialized = ov.ok_or(anyhow::anyhow!(NO_SUCH_USER))?;
108 Ok(Some(Box::new(RedisSession {
109 inner: serde_json::from_str(serialized.as_str())?,
110 sid: sid.to_string(),
111 })))
112 }
113
114 async fn save_session(
115 &self,
116 session: &Box<dyn Session>,
117 res: &mut HttpResonse,
118 ) -> anyhow::Result<()> {
119 let session = session
120 .as_any()
121 .downcast_ref::<RedisSession>()
122 .ok_or(anyhow::anyhow!("downcast failed"))?;
123 let sid = session.sid.as_str();
124 let value = session.value();
125 let mut conn = self.redis_cli.get_async_connection().await?;
126 let serialized = serde_json::to_string(value)?;
127 conn.set(build_session_key(&sid), serialized).await?;
128
129 let cookie = cookie::Cookie::new("session_id", sid.to_string());
130 res.headers_mut().append(
131 header::SET_COOKIE,
132 header::HeaderValue::from_str(cookie.to_string().as_str())?,
133 );
134
135 Ok(())
136 }
137}
138
139#[derive(Debug)]
141pub struct CookieSession {
142 inner: Value,
143}
144
145impl CookieSession {
146 pub fn new() -> Self {
147 CookieSession { inner: json!({}) }
148 }
149 pub fn from_value(value: Value) -> Self {
150 CookieSession { inner: value }
151 }
152}
153
154#[async_trait]
155impl Session for CookieSession {
156 fn get(&self, key: &str) -> anyhow::Result<Option<Value>> {
157 Ok(self.inner.get(key).cloned())
158 }
159
160 fn set(&mut self, key: &str, value: Value) -> anyhow::Result<()> {
161 Ok(self.inner[key] = value)
162 }
163
164 fn value(&self) -> &Value {
165 &self.inner
166 }
167 fn as_any(&self) -> &dyn any::Any {
168 self
169 }
170}
171
172pub struct CookieSessionProvider {
173 key: hmac::Hmac<Sha256>,
174}
175
176impl CookieSessionProvider {
177 pub fn new(key: hmac::Hmac<Sha256>) -> Self {
178 CookieSessionProvider { key }
179 }
180
181 pub fn from_slice(slice: &[u8]) -> anyhow::Result<Self> {
182 let key = hmac::Hmac::<Sha256>::new_from_slice(slice)?;
183 Ok(CookieSessionProvider::new(key))
184 }
185
186 pub fn from_hex(hex: &str) -> anyhow::Result<Self> {
187 Ok(CookieSessionProvider::from_slice(&hex::decode(hex)?)?)
188 }
189}
190
191impl CookieSessionProvider {
192 fn cookie_name(&self) -> &'static str {
193 "signed_session"
194 }
195 fn separator(&self) -> &'static str {
196 "."
197 }
198 fn new_session(&self) -> Box<dyn Session> {
199 Box::new(CookieSession::new())
200 }
201}
202
203#[async_trait]
204impl SessionProvider for CookieSessionProvider {
205 async fn open_session(
206 &self,
207 cookie_map: &CookieMap,
208 ) -> anyhow::Result<Option<Box<dyn Session>>> {
209 let signed_session = match cookie_map.get(self.cookie_name()) {
210 Some(v) => v,
211 None => return Ok(Some(self.new_session())),
212 };
213 let v = signed_session
214 .split(self.separator())
215 .collect::<Vec<&str>>();
216 let (session, signature) = match v.as_slice() {
217 [session_b6, signature_b6] => (b6_to_s(session_b6)?, b6_to_vu8(&signature_b6)?),
218 _ => return Err(anyhow::anyhow!("Signed session format is invalid")),
219 };
220
221 let signature_actual = get_signature(&self.key, &session);
222 if signature != signature_actual {
223 return Err(anyhow::anyhow!("Signature is invalid"));
224 }
225
226 Ok(Some(Box::new(CookieSession::from_value(
227 serde_json::from_str(session.as_str())?,
228 ))))
229 }
230
231 async fn save_session(
232 &self,
233 session: &Box<dyn Session>,
234 res: &mut HttpResonse,
235 ) -> anyhow::Result<()> {
236 let session_value = serde_json::to_string(session.value())?;
237 let sigature = get_signature(&self.key, &session_value);
238 let cookie_value = format!(
239 "{}{}{}",
240 s_to_b6(&session_value),
241 self.separator(),
242 vu8_to_b6(&sigature)
243 );
244 let cookie = cookie::Cookie::new(self.cookie_name(), cookie_value);
245 res.headers_mut().append(
246 header::SET_COOKIE,
247 header::HeaderValue::from_str(cookie.to_string().as_str())?,
248 );
249 Ok(())
250 }
251}
252
253fn s_to_b6(s: &str) -> String {
254 URL_SAFE_NO_PAD.encode(s.as_bytes())
255}
256
257fn vu8_to_b6(v: &[u8]) -> String {
258 URL_SAFE_NO_PAD.encode(v)
259}
260
261fn b6_to_vu8(s: &str) -> anyhow::Result<Vec<u8>> {
262 Ok(URL_SAFE_NO_PAD.decode(s.as_bytes())?)
263}
264
265fn b6_to_s(s: &str) -> anyhow::Result<String> {
266 let json_bytes = URL_SAFE_NO_PAD.decode(s.as_bytes())?;
267 Ok(String::from_utf8(json_bytes)?)
268}
269
270pub fn get_signature(key: &hmac::Hmac<Sha256>, session: &str) -> Vec<u8> {
271 let mut key = key.clone();
272 key.update(session.as_bytes());
273 let signature = key.finalize().into_bytes();
274 signature.to_vec()
275}