From 01f9f3549c76702fd56e58d44c5180fecddb4bfa Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Tue, 22 Oct 2024 23:25:24 -0400 Subject: Sort out the naming of the various parts of an identity. * A `cookie::Identity` (`IdentityCookie`) is a specialized CookieJar for working with identities. * An `Identity` is a token/login pair. I hope for this to be a bit more legible. In service of this, `Login` is no longer extractable. You have to get an identity. --- src/token/extract/cookie.rs | 94 +++++++++++++++++++++++++++++++++++++ src/token/extract/identity.rs | 6 +-- src/token/extract/identity_token.rs | 94 ------------------------------------- src/token/extract/mod.rs | 4 +- 4 files changed, 99 insertions(+), 99 deletions(-) create mode 100644 src/token/extract/cookie.rs delete mode 100644 src/token/extract/identity_token.rs (limited to 'src/token/extract') 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 { + 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) + } +} diff --git a/src/token/extract/identity.rs b/src/token/extract/identity.rs index 8b3cd94..a69f509 100644 --- a/src/token/extract/identity.rs +++ b/src/token/extract/identity.rs @@ -4,7 +4,7 @@ use axum::{ response::{IntoResponse, Response}, }; -use super::IdentityToken; +use super::IdentityCookie; use crate::{ app::App, @@ -25,10 +25,10 @@ impl FromRequestParts for Identity { type Rejection = LoginError; async fn from_request_parts(parts: &mut Parts, state: &App) -> Result { - let Ok(identity_token) = IdentityToken::from_request_parts(parts, state).await; + let Ok(cookie) = IdentityCookie::from_request_parts(parts, state).await; let RequestedAt(used_at) = RequestedAt::from_request_parts(parts, state).await?; - let secret = identity_token.secret().ok_or(LoginError::Unauthorized)?; + let secret = cookie.secret().ok_or(LoginError::Unauthorized)?; let app = State::::from_request_parts(parts, state).await?; match app.tokens().validate(&secret, &used_at).await { diff --git a/src/token/extract/identity_token.rs b/src/token/extract/identity_token.rs deleted file mode 100644 index a1e900e..0000000 --- a/src/token/extract/identity_token.rs +++ /dev/null @@ -1,94 +0,0 @@ -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 IdentityToken { - cookies: CookieJar, -} - -impl fmt::Debug for IdentityToken { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("IdentityToken") - .field("identity", &self.secret()) - .finish() - } -} - -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 { - self.cookies - .get(IDENTITY_COOKIE) - .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((IDENTITY_COOKIE, 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(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) - } -} diff --git a/src/token/extract/mod.rs b/src/token/extract/mod.rs index b4800ae..fc0f52b 100644 --- a/src/token/extract/mod.rs +++ b/src/token/extract/mod.rs @@ -1,4 +1,4 @@ +mod cookie; mod identity; -mod identity_token; -pub use self::{identity::Identity, identity_token::IdentityToken}; +pub use self::{cookie::Identity as IdentityCookie, identity::Identity}; -- cgit v1.2.3