use super::{cookies_from_request, set_cookies, Cookie, Key}; use axum::{ extract::{FromRef, FromRequestParts}, response::{IntoResponse, IntoResponseParts, Response, ResponseParts}, }; use cookie::PrivateJar; use http::{request::Parts, HeaderMap}; use std::{convert::Infallible, fmt, marker::PhantomData}; /// Extractor that grabs private cookies from the request and manages the jar. /// /// All cookies will be private and encrypted with a [`Key`]. This makes it suitable for storing /// private data. /// /// Note that methods like [`PrivateCookieJar::add`], [`PrivateCookieJar::remove`], etc updates the /// [`PrivateCookieJar`] and returns it. This value _must_ be returned from the handler as part of /// the response for the changes to be propagated. /// /// # Example /// /// ```rust /// use axum::{ /// Router, /// routing::{post, get}, /// extract::FromRef, /// response::{IntoResponse, Redirect}, /// http::StatusCode, /// }; /// use axum_extra::{ /// TypedHeader, /// headers::authorization::{Authorization, Bearer}, /// extract::cookie::{PrivateCookieJar, Cookie, Key}, /// }; /// /// async fn set_secret( /// jar: PrivateCookieJar, /// ) -> (PrivateCookieJar, Redirect) { /// let updated_jar = jar.add(Cookie::new("secret", "secret-data")); /// (updated_jar, Redirect::to("/get")) /// } /// /// async fn get_secret(jar: PrivateCookieJar) { /// if let Some(data) = jar.get("secret") { /// // ... /// } /// } /// /// // our application state /// #[derive(Clone)] /// struct AppState { /// // that holds the key used to sign cookies /// key: Key, /// } /// /// // this impl tells `SignedCookieJar` how to access the key from our state /// impl FromRef for Key { /// fn from_ref(state: &AppState) -> Self { /// state.key.clone() /// } /// } /// /// let state = AppState { /// // Generate a secure key /// // /// // You probably don't wanna generate a new one each time the app starts though /// key: Key::generate(), /// }; /// /// let app = Router::new() /// .route("/set", post(set_secret)) /// .route("/get", get(get_secret)) /// .with_state(state); /// # let _: axum::Router = app; /// ``` /// /// If you have been using `Arc` you cannot implement `FromRef> for Key`. /// You can use a new type instead: /// /// ```rust /// # use axum::extract::FromRef; /// # use axum_extra::extract::cookie::{PrivateCookieJar, Cookie, Key}; /// use std::sync::Arc; /// use std::ops::Deref; /// /// #[derive(Clone)] /// struct AppState(Arc); /// /// // deref so you can still access the inner fields easily /// impl Deref for AppState { /// type Target = InnerState; /// /// fn deref(&self) -> &Self::Target { /// &self.0 /// } /// } /// /// struct InnerState { /// key: Key /// } /// /// impl FromRef for Key { /// fn from_ref(state: &AppState) -> Self { /// state.0.key.clone() /// } /// } /// ``` pub struct PrivateCookieJar { jar: cookie::CookieJar, key: Key, // The key used to extract the key. Allows users to use multiple keys for different // jars. Maybe a library wants its own key. _marker: PhantomData, } impl fmt::Debug for PrivateCookieJar { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("PrivateCookieJar") .field("jar", &self.jar) .field("key", &"REDACTED") .finish() } } impl FromRequestParts for PrivateCookieJar where S: Send + Sync, K: FromRef + Into, { type Rejection = Infallible; async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { let k = K::from_ref(state); let key = k.into(); let PrivateCookieJar { jar, key, _marker: _, } = PrivateCookieJar::from_headers(&parts.headers, key); Ok(PrivateCookieJar { jar, key, _marker: PhantomData, }) } } impl PrivateCookieJar { /// Create a new `PrivateCookieJar` from a map of request headers. /// /// The valid cookies in `headers` will be added to the jar. /// /// This is intended to be used in middleware and other where places it might be difficult to /// run extractors. Normally you should create `PrivateCookieJar`s through [`FromRequestParts`]. /// /// [`FromRequestParts`]: axum::extract::FromRequestParts pub fn from_headers(headers: &HeaderMap, key: Key) -> Self { let mut jar = cookie::CookieJar::new(); let mut private_jar = jar.private_mut(&key); for cookie in cookies_from_request(headers) { if let Some(cookie) = private_jar.decrypt(cookie) { private_jar.add_original(cookie); } } Self { jar, key, _marker: PhantomData, } } /// Create a new empty `PrivateCookieJarIter`. /// /// This is intended to be used in middleware and other places where it might be difficult to /// run extractors. Normally you should create `PrivateCookieJar`s through [`FromRequestParts`]. /// /// [`FromRequestParts`]: axum::extract::FromRequestParts pub fn new(key: Key) -> Self { Self { jar: Default::default(), key, _marker: PhantomData, } } } impl PrivateCookieJar { /// Get a cookie from the jar. /// /// If the cookie exists and can be decrypted then it is returned in plaintext. /// /// # Example /// /// ```rust /// use axum_extra::extract::cookie::PrivateCookieJar; /// use axum::response::IntoResponse; /// /// async fn handle(jar: PrivateCookieJar) { /// let value: Option = jar /// .get("foo") /// .map(|cookie| cookie.value().to_owned()); /// } /// ``` pub fn get(&self, name: &str) -> Option> { self.private_jar().get(name) } /// Remove a cookie from the jar. /// /// # Example /// /// ```rust /// use axum_extra::extract::cookie::{PrivateCookieJar, Cookie}; /// use axum::response::IntoResponse; /// /// async fn handle(jar: PrivateCookieJar) -> PrivateCookieJar { /// jar.remove(Cookie::from("foo")) /// } /// ``` #[must_use] pub fn remove>>(mut self, cookie: C) -> Self { self.private_jar_mut().remove(cookie); self } /// Add a cookie to the jar. /// /// The value will automatically be percent-encoded. /// /// # Example /// /// ```rust /// use axum_extra::extract::cookie::{PrivateCookieJar, Cookie}; /// use axum::response::IntoResponse; /// /// async fn handle(jar: PrivateCookieJar) -> PrivateCookieJar { /// jar.add(Cookie::new("foo", "bar")) /// } /// ``` #[must_use] #[allow(clippy::should_implement_trait)] pub fn add>>(mut self, cookie: C) -> Self { self.private_jar_mut().add(cookie); self } /// Authenticates and decrypts `cookie`, returning the plaintext version if decryption succeeds /// or `None` otherwise. pub fn decrypt(&self, cookie: Cookie<'static>) -> Option> { self.private_jar().decrypt(cookie) } /// Get an iterator over all cookies in the jar. /// /// Only cookies with valid authenticity and integrity are yielded by the iterator. pub fn iter(&self) -> impl Iterator> + '_ { PrivateCookieJarIter { jar: self, iter: self.jar.iter(), } } fn private_jar(&self) -> PrivateJar<&'_ cookie::CookieJar> { self.jar.private(&self.key) } fn private_jar_mut(&mut self) -> PrivateJar<&'_ mut cookie::CookieJar> { self.jar.private_mut(&self.key) } } impl IntoResponseParts for PrivateCookieJar { type Error = Infallible; fn into_response_parts(self, mut res: ResponseParts) -> Result { set_cookies(self.jar, res.headers_mut()); Ok(res) } } impl IntoResponse for PrivateCookieJar { fn into_response(self) -> Response { (self, ()).into_response() } } struct PrivateCookieJarIter<'a, K> { jar: &'a PrivateCookieJar, iter: cookie::Iter<'a>, } impl<'a, K> Iterator for PrivateCookieJarIter<'a, K> { type Item = Cookie<'static>; fn next(&mut self) -> Option { loop { let cookie = self.iter.next()?; if let Some(cookie) = self.jar.get(cookie.name()) { return Some(cookie); } } } } impl Clone for PrivateCookieJar { fn clone(&self) -> Self { Self { jar: self.jar.clone(), key: self.key.clone(), _marker: self._marker, } } }