-
Notifications
You must be signed in to change notification settings - Fork 619
/
Copy pathlib.rs
117 lines (101 loc) · 3.33 KB
/
lib.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#![doc = include_str!("../README.md")]
use axum::extract::{Extension, FromRequestParts, Request};
use axum::middleware::Next;
use axum::response::{IntoResponse, Response};
use axum_extra::extract::SignedCookieJar;
use base64::{engine::general_purpose, Engine};
use cookie::time::Duration;
use cookie::{Cookie, SameSite};
use parking_lot::RwLock;
use std::collections::HashMap;
use std::sync::Arc;
pub static COOKIE_NAME: &str = "cargo_session";
static MAX_AGE_DAYS: i64 = 90;
#[derive(Clone, FromRequestParts)]
#[from_request(via(Extension))]
pub struct SessionExtension(Arc<RwLock<Session>>);
impl SessionExtension {
fn new(session: Session) -> Self {
Self(Arc::new(RwLock::new(session)))
}
pub fn get(&self, key: &str) -> Option<String> {
let session = self.0.read();
session.data.get(key).cloned()
}
pub fn insert(&self, key: String, value: String) -> Option<String> {
let mut session = self.0.write();
session.dirty = true;
session.data.insert(key, value)
}
pub fn remove(&self, key: &str) -> Option<String> {
let mut session = self.0.write();
session.dirty = true;
session.data.remove(key)
}
}
pub async fn attach_session(jar: SignedCookieJar, mut req: Request, next: Next) -> Response {
// Decode session cookie
let data = jar.get(COOKIE_NAME).map(decode).unwrap_or_default();
// Save decoded session data in request extension,
// and keep an `Arc` clone for later
let session = SessionExtension::new(Session::new(data));
req.extensions_mut().insert(session.clone());
// Process the request
let response = next.run(req).await;
// Check if the session data was mutated
let session = session.0.read();
if session.dirty {
// Return response with additional `Set-Cookie` header
let encoded = encode(&session.data);
let cookie = Cookie::build((COOKIE_NAME, encoded))
.http_only(true)
.secure(true)
.same_site(SameSite::Strict)
.max_age(Duration::days(MAX_AGE_DAYS))
.path("/");
(jar.add(cookie), response).into_response()
} else {
response
}
}
/// Request extension holding the session data
pub struct Session {
data: HashMap<String, String>,
dirty: bool,
}
impl Session {
fn new(data: HashMap<String, String>) -> Self {
Self { data, dirty: false }
}
}
pub fn decode(cookie: Cookie<'_>) -> HashMap<String, String> {
let mut ret = HashMap::new();
let bytes = general_purpose::STANDARD
.decode(cookie.value().as_bytes())
.unwrap_or_default();
let mut parts = bytes.split(|&a| a == 0xff);
while let (Some(key), Some(value)) = (parts.next(), parts.next()) {
if key.is_empty() {
break;
}
if let (Ok(key), Ok(value)) = (std::str::from_utf8(key), std::str::from_utf8(value)) {
ret.insert(key.to_string(), value.to_string());
}
}
ret
}
pub fn encode(h: &HashMap<String, String>) -> String {
let mut ret = Vec::new();
for (i, (k, v)) in h.iter().enumerate() {
if i != 0 {
ret.push(0xff)
}
ret.extend(k.bytes());
ret.push(0xff);
ret.extend(v.bytes());
}
while ret.len() * 8 % 6 != 0 {
ret.push(0xff);
}
general_purpose::STANDARD.encode(&ret[..])
}