use axum::{ extract::FromRequestParts, http::request::Parts, response::{IntoResponseParts, ResponseParts}, }; use axum_extra::extract::cookie::{Cookie, CookieJar}; // The usage pattern here - receive the extractor as an argument, return it in // the response - is heavily modelled after CookieJar's own intended usage. #[derive(Clone, Debug)] pub struct IdentityToken { cookies: CookieJar, } impl IdentityToken { // Creates a new, unpopulated identity token store. #[cfg(test)] pub fn new() -> Self { Self { cookies: CookieJar::new(), } } // Get the identity secret sent in the request, if any. If the identity // was not sent, or if it has previously been [clear]ed, then this will // return [None]. If the identity has previously been [set], then this // will return that secret, regardless of what the request originally // included. pub fn secret(&self) -> Option<&str> { self.cookies.get(IDENTITY_COOKIE).map(Cookie::value) } // Positively set the identity secret, and ensure that it will be sent // back to the client when this extractor is included in a response. pub fn set(self, secret: &str) -> Self { let identity_cookie = Cookie::build((IDENTITY_COOKIE, String::from(secret))) .http_only(true) .path("/api/") .permanent() .build(); Self { cookies: self.cookies.add(identity_cookie), } } // Remove the identity secret and ensure that it will be cleared when this // extractor is included in a response. pub fn clear(self) -> Self { Self { cookies: self.cookies.remove(IDENTITY_COOKIE), } } } const IDENTITY_COOKIE: &str = "identity"; #[async_trait::async_trait] impl FromRequestParts for IdentityToken where S: Send + Sync, { type Rejection = >::Rejection; async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { let cookies = CookieJar::from_request_parts(parts, state).await?; Ok(Self { cookies }) } } impl IntoResponseParts for IdentityToken { type Error = ::Error; fn into_response_parts(self, res: ResponseParts) -> Result { let Self { cookies } = self; cookies.into_response_parts(res) } }