use std::fmt; use axum::{ extract::FromRequestParts, http::request::Parts, response::{IntoResponseParts, ResponseParts}, }; use axum_extra::extract::cookie::{Cookie, CookieJar}; use crate::token::Secret; // 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)] pub struct Identity { cookies: CookieJar, } impl fmt::Debug for Identity { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("IdentityCookie") .field("identity", &self.secret()) .finish() } } impl Identity { const COOKIE_NAME: &str = "identity"; // 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 { self.cookies .get(Self::COOKIE_NAME) .map(Cookie::value) .map(Secret::from) } // 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: impl Into) -> Self { let secret = secret.into().reveal(); let identity_cookie = Cookie::build((Self::COOKIE_NAME, secret)) .http_only(true) .path("/") .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(Self::COOKIE_NAME), } } } #[async_trait::async_trait] impl FromRequestParts for Identity 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 Identity { type Error = ::Error; fn into_response_parts(self, res: ResponseParts) -> Result { let Self { cookies } = self; cookies.into_response_parts(res) } }