Built for people who think better out loud.
at main 93 lines 2.3 kB view raw
1use axum::extract::{FromRef, FromRequestParts}; 2use axum::http::{request::Parts, StatusCode}; 3use axum::response::{IntoResponse, Response}; 4use axum::Json; 5use serde::Serialize; 6 7use crate::application::auth::{AuthError, load_current_user}; 8use crate::domain::CurrentUser; 9use crate::http::cookies::read_session_cookie; 10use crate::config::Config; 11use sqlx::PgPool; 12 13/// Extracts the authenticated user from the session cookie. 14#[derive(Debug, Clone)] 15pub struct AuthenticatedUser(pub CurrentUser); 16 17/// HTTP rejection for authentication failures. 18#[derive(Debug)] 19pub struct AuthRejection { 20 status: StatusCode, 21 body: Json<ErrorResponse>, 22} 23 24impl IntoResponse for AuthRejection { 25 fn into_response(self) -> Response { 26 (self.status, self.body).into_response() 27 } 28} 29 30impl AuthRejection { 31 pub fn status(&self) -> StatusCode { 32 self.status 33 } 34 35 pub fn message(&self) -> String { 36 self.body.error.message.clone() 37 } 38} 39 40impl<S> FromRequestParts<S> for AuthenticatedUser 41where 42 S: Send + Sync, 43 Config: FromRef<S>, 44 PgPool: FromRef<S>, 45{ 46 type Rejection = AuthRejection; 47 48 async fn from_request_parts( 49 parts: &mut Parts, 50 state: &S, 51 ) -> Result<Self, Self::Rejection> { 52 let config = Config::from_ref(state); 53 let db_pool = PgPool::from_ref(state); 54 let session_id = read_session_cookie(&parts.headers, &config.oauth_cookie_name) 55 .ok_or_else(missing_session_cookie)?; 56 let user = load_current_user(&db_pool, session_id) 57 .await 58 .map_err(map_auth_error)?; 59 Ok(Self(user)) 60 } 61} 62 63#[derive(Debug, Serialize)] 64struct ErrorResponse { 65 error: ErrorDetail, 66} 67 68#[derive(Debug, Serialize)] 69struct ErrorDetail { 70 message: String, 71} 72 73fn map_auth_error(error: AuthError) -> AuthRejection { 74 AuthRejection { 75 status: error.status(), 76 body: Json(ErrorResponse { 77 error: ErrorDetail { 78 message: error.message(), 79 }, 80 }), 81 } 82} 83 84fn missing_session_cookie() -> AuthRejection { 85 AuthRejection { 86 status: StatusCode::UNAUTHORIZED, 87 body: Json(ErrorResponse { 88 error: ErrorDetail { 89 message: "Missing session cookie.".to_string(), 90 }, 91 }), 92 } 93}