feat(presentation): add deps_struct! macro; add api_key_auth + engagement to AppState; use ApiKeyService in extractor
This commit is contained in:
@@ -17,8 +17,6 @@ uuid = { workspace = true }
|
|||||||
chrono = { workspace = true }
|
chrono = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
async-trait = { workspace = true }
|
async-trait = { workspace = true }
|
||||||
sha2 = "0.10"
|
|
||||||
hex = "0.4"
|
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
utoipa = { version = "5.5.0", features = ["axum_extras", "uuid", "chrono"] }
|
utoipa = { version = "5.5.0", features = ["axum_extras", "uuid", "chrono"] }
|
||||||
utoipa-scalar = { version = "0.3.0", features = ["axum"], default-features = false }
|
utoipa-scalar = { version = "0.3.0", features = ["axum"], default-features = false }
|
||||||
|
|||||||
@@ -2,6 +2,27 @@ use crate::{errors::ApiError, state::AppState};
|
|||||||
use axum::{extract::FromRequestParts, http::request::Parts};
|
use axum::{extract::FromRequestParts, http::request::Parts};
|
||||||
use domain::value_objects::UserId;
|
use domain::value_objects::UserId;
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// deps_struct! — generates Deps struct + impl FromAppState from a field list.
|
||||||
|
// Field names must match AppState exactly (enforced at compile time).
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! deps_struct {
|
||||||
|
( $name:ident { $( $field:ident : $trait:path ),+ $(,)? } ) => {
|
||||||
|
pub struct $name {
|
||||||
|
$( pub $field: ::std::sync::Arc<dyn $trait>, )+
|
||||||
|
}
|
||||||
|
impl $crate::extractors::FromAppState for $name {
|
||||||
|
fn from_state(s: &$crate::state::AppState) -> Self {
|
||||||
|
Self {
|
||||||
|
$( $field: ::std::sync::Arc::clone(&s.$field), )+
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Deps<S> extractor — narrows AppState to a handler-specific deps struct
|
// Deps<S> extractor — narrows AppState to a handler-specific deps struct
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -23,6 +44,10 @@ impl<S: FromAppState + Send + 'static> FromRequestParts<AppState> for Deps<S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Auth extractors
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
pub struct AuthUser(pub UserId);
|
pub struct AuthUser(pub UserId);
|
||||||
pub struct OptionalAuthUser(pub Option<UserId>);
|
pub struct OptionalAuthUser(pub Option<UserId>);
|
||||||
|
|
||||||
@@ -57,17 +82,12 @@ async fn extract_user_id(parts: &mut Parts, state: &AppState) -> Result<Option<U
|
|||||||
}
|
}
|
||||||
if let Some(key_header) = parts.headers.get("X-Api-Key") {
|
if let Some(key_header) = parts.headers.get("X-Api-Key") {
|
||||||
if let Ok(raw) = key_header.to_str() {
|
if let Ok(raw) = key_header.to_str() {
|
||||||
let hash = sha256_hex(raw);
|
return state
|
||||||
if let Ok(Some(key)) = state.api_keys.find_by_hash(&hash).await {
|
.api_key_auth
|
||||||
return Ok(Some(key.user_id));
|
.validate_key(raw)
|
||||||
}
|
.await
|
||||||
|
.map_err(|_| ApiError::Unauthorized);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sha256_hex(s: &str) -> String {
|
|
||||||
use sha2::{Digest, Sha256};
|
|
||||||
let hash = Sha256::digest(s.as_bytes());
|
|
||||||
hex::encode(hash)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ pub struct AppState {
|
|||||||
pub blocks: Arc<dyn BlockRepository>,
|
pub blocks: Arc<dyn BlockRepository>,
|
||||||
pub tags: Arc<dyn TagRepository>,
|
pub tags: Arc<dyn TagRepository>,
|
||||||
pub api_keys: Arc<dyn ApiKeyRepository>,
|
pub api_keys: Arc<dyn ApiKeyRepository>,
|
||||||
|
pub api_key_auth: Arc<dyn ApiKeyService>,
|
||||||
pub top_friends: Arc<dyn TopFriendRepository>,
|
pub top_friends: Arc<dyn TopFriendRepository>,
|
||||||
pub notifications: Arc<dyn NotificationRepository>,
|
pub notifications: Arc<dyn NotificationRepository>,
|
||||||
pub remote_actors: Arc<dyn RemoteActorRepository>,
|
pub remote_actors: Arc<dyn RemoteActorRepository>,
|
||||||
@@ -25,4 +26,5 @@ pub struct AppState {
|
|||||||
pub ap_repo: Arc<dyn ActivityPubRepository>,
|
pub ap_repo: Arc<dyn ActivityPubRepository>,
|
||||||
pub remote_actor_connections: Arc<dyn RemoteActorConnectionRepository>,
|
pub remote_actor_connections: Arc<dyn RemoteActorConnectionRepository>,
|
||||||
pub federation_scheduler: Arc<dyn FederationSchedulerPort>,
|
pub federation_scheduler: Arc<dyn FederationSchedulerPort>,
|
||||||
|
pub engagement: Arc<dyn EngagementRepository>,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user