summaryrefslogtreecommitdiff
path: root/src/token/extract/cookie.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/token/extract/cookie.rs')
-rw-r--r--src/token/extract/cookie.rs94
1 files changed, 94 insertions, 0 deletions
diff --git a/src/token/extract/cookie.rs b/src/token/extract/cookie.rs
new file mode 100644
index 0000000..af5787d
--- /dev/null
+++ b/src/token/extract/cookie.rs
@@ -0,0 +1,94 @@
+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<Secret> {
+ 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<Secret>) -> 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<S> FromRequestParts<S> for Identity
+where
+ S: Send + Sync,
+{
+ type Rejection = <CookieJar as FromRequestParts<S>>::Rejection;
+
+ async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
+ let cookies = CookieJar::from_request_parts(parts, state).await?;
+ Ok(Self { cookies })
+ }
+}
+
+impl IntoResponseParts for Identity {
+ type Error = <CookieJar as IntoResponseParts>::Error;
+
+ fn into_response_parts(self, res: ResponseParts) -> Result<ResponseParts, Self::Error> {
+ let Self { cookies } = self;
+ cookies.into_response_parts(res)
+ }
+}