simple_api/
session.rs

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    // flask里SecureCookieSessionInterface是用的策略是:cookie里找不到session内容,或者session内容解码失败,都创建一个新的session
64    // 这里也都参考这个来实现吧
65    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>, // Better to use a connection pool
78}
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// Below is CookieSessionProvider
140#[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}