feat(presentation): state, errors, extractors, auth and user handlers
This commit is contained in:
47
crates/presentation/src/extractors.rs
Normal file
47
crates/presentation/src/extractors.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
use axum::{extract::FromRequestParts, http::request::Parts};
|
||||
use domain::value_objects::UserId;
|
||||
use crate::{errors::ApiError, state::AppState};
|
||||
|
||||
pub struct AuthUser(pub UserId);
|
||||
pub struct OptionalAuthUser(pub Option<UserId>);
|
||||
|
||||
impl FromRequestParts<AppState> for AuthUser {
|
||||
type Rejection = ApiError;
|
||||
async fn from_request_parts(parts: &mut Parts, state: &AppState) -> Result<Self, ApiError> {
|
||||
extract_user_id(parts, state).await?
|
||||
.ok_or(ApiError::Unauthorized)
|
||||
.map(AuthUser)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRequestParts<AppState> for OptionalAuthUser {
|
||||
type Rejection = ApiError;
|
||||
async fn from_request_parts(parts: &mut Parts, state: &AppState) -> Result<Self, ApiError> {
|
||||
Ok(OptionalAuthUser(extract_user_id(parts, state).await?))
|
||||
}
|
||||
}
|
||||
|
||||
async fn extract_user_id(parts: &mut Parts, state: &AppState) -> Result<Option<UserId>, ApiError> {
|
||||
if let Some(auth_header) = parts.headers.get("Authorization") {
|
||||
if let Ok(s) = auth_header.to_str() {
|
||||
if let Some(token) = s.strip_prefix("Bearer ") {
|
||||
return state.auth.validate_token(token).map(Some).map_err(|_| ApiError::Unauthorized);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(key_header) = parts.headers.get("X-Api-Key") {
|
||||
if let Ok(raw) = key_header.to_str() {
|
||||
let hash = sha256_hex(raw);
|
||||
if let Ok(Some(key)) = state.api_keys.find_by_hash(&hash).await {
|
||||
return Ok(Some(key.user_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn sha256_hex(s: &str) -> String {
|
||||
use sha2::{Digest, Sha256};
|
||||
let hash = Sha256::digest(s.as_bytes());
|
||||
hex::encode(hash)
|
||||
}
|
||||
Reference in New Issue
Block a user