refactor: http-axum split into api/html/federation modules, router.rs, templates per page group
This commit is contained in:
158
crates/adapters/http-axum/src/extractors/mod.rs
Normal file
158
crates/adapters/http-axum/src/extractors/mod.rs
Normal file
@@ -0,0 +1,158 @@
|
||||
use axum::{
|
||||
extract::{FromRef, FromRequestParts},
|
||||
http::{StatusCode, header, header::AUTHORIZATION, request::Parts},
|
||||
response::{IntoResponse, Redirect},
|
||||
};
|
||||
use domain::{errors::DomainError, value_objects::UserId};
|
||||
|
||||
use crate::{errors::ApiError, state::AppState};
|
||||
|
||||
pub struct AuthenticatedUser(pub UserId);
|
||||
|
||||
impl<S> FromRequestParts<S> for AuthenticatedUser
|
||||
where
|
||||
AppState: FromRef<S>,
|
||||
S: Send + Sync,
|
||||
{
|
||||
type Rejection = ApiError;
|
||||
|
||||
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
||||
let app_state = AppState::from_ref(state);
|
||||
let token = parts
|
||||
.headers
|
||||
.get(AUTHORIZATION)
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.and_then(|v| v.strip_prefix("Bearer "))
|
||||
.ok_or_else(|| {
|
||||
ApiError(DomainError::Unauthorized(
|
||||
"Missing or invalid auth token".into(),
|
||||
))
|
||||
})?;
|
||||
let user_id = app_state
|
||||
.app_ctx
|
||||
.services
|
||||
.auth
|
||||
.validate_token(token)
|
||||
.await?;
|
||||
Ok(AuthenticatedUser(user_id))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OptionalCookieUser(pub Option<UserId>);
|
||||
pub struct RequiredCookieUser(pub UserId);
|
||||
|
||||
fn extract_token_from_cookie(parts: &Parts) -> Option<String> {
|
||||
parts
|
||||
.headers
|
||||
.get(header::COOKIE)
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.and_then(|cookies| {
|
||||
cookies
|
||||
.split(';')
|
||||
.find_map(|c| c.trim().strip_prefix("token=").map(str::to_string))
|
||||
})
|
||||
}
|
||||
|
||||
impl<S> FromRequestParts<S> for OptionalCookieUser
|
||||
where
|
||||
AppState: FromRef<S>,
|
||||
S: Send + Sync,
|
||||
{
|
||||
type Rejection = std::convert::Infallible;
|
||||
|
||||
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
||||
let app_state = AppState::from_ref(state);
|
||||
let Some(token) = extract_token_from_cookie(parts) else {
|
||||
return Ok(OptionalCookieUser(None));
|
||||
};
|
||||
let user_id = app_state
|
||||
.app_ctx
|
||||
.services
|
||||
.auth
|
||||
.validate_token(&token)
|
||||
.await
|
||||
.ok();
|
||||
Ok(OptionalCookieUser(user_id))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> FromRequestParts<S> for RequiredCookieUser
|
||||
where
|
||||
AppState: FromRef<S>,
|
||||
S: Send + Sync,
|
||||
{
|
||||
type Rejection = axum::response::Response;
|
||||
|
||||
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
||||
let app_state = AppState::from_ref(state);
|
||||
let token = extract_token_from_cookie(parts)
|
||||
.ok_or_else(|| Redirect::to("/login").into_response())?;
|
||||
let user_id = app_state
|
||||
.app_ctx
|
||||
.services
|
||||
.auth
|
||||
.validate_token(&token)
|
||||
.await
|
||||
.map_err(|_| Redirect::to("/login").into_response())?;
|
||||
Ok(RequiredCookieUser(user_id))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AdminApiUser(pub UserId);
|
||||
|
||||
impl<S> FromRequestParts<S> for AdminApiUser
|
||||
where
|
||||
AppState: FromRef<S>,
|
||||
S: Send + Sync,
|
||||
{
|
||||
type Rejection = ApiError;
|
||||
|
||||
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
||||
let AuthenticatedUser(user_id) =
|
||||
AuthenticatedUser::from_request_parts(parts, state).await?;
|
||||
let app_state = AppState::from_ref(state);
|
||||
let is_admin = application::users::authorize_admin::execute(
|
||||
&app_state.app_ctx.deps.users.authorize_admin,
|
||||
user_id.value(),
|
||||
)
|
||||
.await
|
||||
.map_err(ApiError)?
|
||||
.ok_or_else(|| ApiError(DomainError::NotFound("user not found".into())))?;
|
||||
if is_admin {
|
||||
Ok(AdminApiUser(user_id))
|
||||
} else {
|
||||
Err(ApiError(DomainError::Forbidden("admin only".into())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AdminUser(pub UserId);
|
||||
|
||||
impl<S> FromRequestParts<S> for AdminUser
|
||||
where
|
||||
AppState: FromRef<S>,
|
||||
S: Send + Sync,
|
||||
{
|
||||
type Rejection = axum::response::Response;
|
||||
|
||||
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
||||
let app_state = AppState::from_ref(state);
|
||||
let RequiredCookieUser(user_id) =
|
||||
RequiredCookieUser::from_request_parts(parts, state).await?;
|
||||
let is_admin = application::users::authorize_admin::execute(
|
||||
&app_state.app_ctx.deps.users.authorize_admin,
|
||||
user_id.value(),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR.into_response())?
|
||||
.ok_or_else(|| StatusCode::UNAUTHORIZED.into_response())?;
|
||||
if is_admin {
|
||||
Ok(AdminUser(user_id))
|
||||
} else {
|
||||
Err(StatusCode::FORBIDDEN.into_response())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests;
|
||||
Reference in New Issue
Block a user