Skip to main content

freshblu_server/handlers/
mod.rs

1pub mod auth;
2pub mod devices;
3pub mod messages;
4pub mod status;
5pub mod subscribe;
6pub mod subscriptions;
7pub mod tokens;
8
9use axum::{
10    extract::FromRequestParts,
11    http::request::Parts,
12    response::{IntoResponse, Response},
13};
14use freshblu_core::{auth::parse_basic_auth, device::Device, error::FreshBluError};
15use uuid::Uuid;
16
17use crate::{ApiError, AppState};
18
19/// Extractor: authenticated device from HTTP Basic Auth header (uuid:token)
20pub struct AuthenticatedDevice(pub Device, pub Option<Uuid>);
21
22#[axum::async_trait]
23impl FromRequestParts<AppState> for AuthenticatedDevice {
24    type Rejection = Response;
25
26    async fn from_request_parts(
27        parts: &mut Parts,
28        state: &AppState,
29    ) -> Result<Self, Self::Rejection> {
30        let headers = &parts.headers;
31
32        // Try Authorization header first
33        let creds = headers
34            .get("authorization")
35            .and_then(|v| v.to_str().ok())
36            .and_then(parse_basic_auth);
37
38        // Fallback: skynet_auth header (legacy)
39        let creds = creds.or_else(|| {
40            headers
41                .get("skynet_auth_uuid")
42                .zip(headers.get("skynet_auth_token"))
43                .and_then(|(u, t)| {
44                    Some((u.to_str().ok()?.to_string(), t.to_str().ok()?.to_string()))
45                })
46        });
47
48        let (uuid_str, token) =
49            creds.ok_or_else(|| ApiError::from(FreshBluError::Unauthorized).into_response())?;
50
51        let uuid = Uuid::parse_str(&uuid_str)
52            .map_err(|_| ApiError::from(FreshBluError::Unauthorized).into_response())?;
53
54        let device = state
55            .store
56            .authenticate(&uuid, &token)
57            .await
58            .map_err(|e| ApiError::from(e).into_response())?
59            .ok_or_else(|| ApiError::from(FreshBluError::Unauthorized).into_response())?;
60
61        // Rate limiting
62        state
63            .rate_limiter
64            .check(&uuid)
65            .map_err(|e| ApiError::from(e).into_response())?;
66
67        // Check x-meshblu-as header for acting as another device
68        let as_uuid = headers
69            .get("x-meshblu-as")
70            .and_then(|v| v.to_str().ok())
71            .and_then(|v| Uuid::parse_str(v).ok());
72
73        Ok(AuthenticatedDevice(device, as_uuid))
74    }
75}