use std::fmt; use axum::{ extract::{FromRequestParts, State}, http::request::Parts, response::{IntoResponse, IntoResponseParts, Response, ResponseParts}, }; use axum_extra::extract::cookie::{Cookie, CookieJar}; use crate::{ app::App, clock::RequestedAt, error::{Internal, Unauthorized}, login::app::ValidateError, repo::{login::Login, token}, }; // 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.cookies.get(IDENTITY_COOKIE).map(|_| "********"), ) .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(IdentitySecret::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 IdentitySecret(secret) = secret.into(); let identity_cookie = Cookie::build((IDENTITY_COOKIE, 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) } } #[derive(sqlx::Type)] #[sqlx(transparent)] pub struct IdentitySecret(String); impl fmt::Debug for IdentitySecret { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_tuple("IdentityToken").field(&"********").finish() } } impl From for IdentitySecret where S: Into, { fn from(value: S) -> Self { Self(value.into()) } } #[derive(Clone, Debug)] pub struct Identity { pub token: token::Id, pub login: Login, } #[async_trait::async_trait] impl FromRequestParts for Identity { type Rejection = LoginError; async fn from_request_parts(parts: &mut Parts, state: &App) -> Result { // After Rust 1.82 (and #[feature(min_exhaustive_patterns)] lands on // stable), the following can be replaced: // // ``` // let Ok(identity_token) = IdentityToken::from_request_parts( // parts, // state, // ).await; // ``` let identity_token = IdentityToken::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 app = State::::from_request_parts(parts, state).await?; match app.logins().validate(&secret, &used_at).await { Ok((token, login)) => Ok(Identity { token, login }), Err(ValidateError::InvalidToken) => Err(LoginError::Unauthorized), Err(other) => Err(other.into()), } } } pub enum LoginError { Failure(E), Unauthorized, } impl IntoResponse for LoginError where E: IntoResponse, { fn into_response(self) -> Response { match self { Self::Unauthorized => Unauthorized.into_response(), Self::Failure(e) => e.into_response(), } } } impl From for LoginError where E: Into, { fn from(err: E) -> Self { Self::Failure(err.into()) } }