1use std::convert::Infallible;
4use std::time::Duration;
5
6use axum::extract::FromRequestParts;
7use axum::http::HeaderValue;
8use axum::http::header::{COOKIE, SET_COOKIE};
9use axum::http::request::Parts;
10use axum::response::{IntoResponseParts, ResponseParts};
11use cookie::{Cookie, CookieJar, Key, SameSite};
12
13use crate::AppState;
14
15#[derive(Clone)]
42pub struct Cookies {
43 jar: CookieJar,
44 key: Key,
45}
46
47impl Cookies {
48 pub fn get(&self, name: &str) -> Option<String> {
50 self.jar.get(name).map(|c| c.value().to_owned())
51 }
52
53 pub fn get_encrypted(&self, name: &str) -> Option<String> {
56 self.jar
57 .private(&self.key)
58 .get(name)
59 .map(|c| c.value().to_owned())
60 }
61}
62
63impl FromRequestParts<AppState> for Cookies {
64 type Rejection = Infallible;
65
66 async fn from_request_parts(parts: &mut Parts, state: &AppState) -> Result<Self, Infallible> {
67 let mut jar = CookieJar::new();
68 for header in parts.headers.get_all(COOKIE) {
69 let Ok(header) = header.to_str() else {
70 continue;
71 };
72 for cookie in Cookie::split_parse_encoded(header.to_owned()).flatten() {
73 jar.add_original(cookie);
74 }
75 }
76 Ok(Self {
77 jar,
78 key: state.key.clone(),
79 })
80 }
81}
82
83#[derive(Debug, Clone)]
88pub struct SetCookie(Cookie<'static>);
89
90impl SetCookie {
91 pub fn new(state: &AppState, name: impl Into<String>, value: impl Into<String>) -> Self {
93 let mut cookie = Cookie::new(name.into(), value.into());
94 cookie.set_path("/");
95 cookie.set_http_only(true);
96 cookie.set_same_site(SameSite::Lax);
97 cookie.set_secure(state.config.url.starts_with("https://"));
98 Self(cookie)
99 }
100
101 pub fn encrypted(state: &AppState, name: impl Into<String>, value: impl Into<String>) -> Self {
104 let plain = Self::new(state, name, value).0;
105 let mut jar = CookieJar::new();
106 jar.private_mut(&state.key).add(plain.clone());
107 let sealed = jar.get(plain.name()).cloned().unwrap_or(plain);
108 Self(sealed)
109 }
110
111 pub fn remove(state: &AppState, name: impl Into<String>) -> Self {
113 let mut cookie = Self::new(state, name, "").0;
114 cookie.make_removal();
115 Self(cookie)
116 }
117
118 pub fn max_age(mut self, age: Duration) -> Self {
120 let seconds = i64::try_from(age.as_secs()).unwrap_or(i64::MAX);
121 self.0.set_max_age(cookie::time::Duration::seconds(seconds));
122 self
123 }
124
125 pub fn path(mut self, path: impl Into<String>) -> Self {
127 self.0.set_path(path.into());
128 self
129 }
130
131 pub fn readable_by_scripts(mut self) -> Self {
134 self.0.set_http_only(false);
135 self
136 }
137
138 pub fn strict(mut self) -> Self {
140 self.0.set_same_site(SameSite::Strict);
141 self
142 }
143}
144
145impl IntoResponseParts for SetCookie {
146 type Error = Infallible;
147
148 fn into_response_parts(self, mut res: ResponseParts) -> Result<ResponseParts, Infallible> {
149 if let Ok(value) = HeaderValue::from_str(&self.0.encoded().to_string()) {
150 res.headers_mut().append(SET_COOKIE, value);
151 }
152 Ok(res)
153 }
154}