Skip to main content

o_sfu/runtime/http_server/operator/
mod.rs

1//! Operator routes protected by the policy of the serving listener.
2
3use std::net::SocketAddr;
4
5use axum::{
6    Router,
7    extract::{Request, State},
8    http::{HeaderValue, StatusCode, header},
9    middleware,
10    response::{IntoResponse, Response},
11    routing::get,
12};
13
14use super::{access::OperatorAccessPolicy, contract::route};
15use crate::runtime::RuntimeState;
16
17mod diagnostics;
18mod metrics;
19mod stats;
20
21/// Stats, metrics and diagnostics with listener-bound operator authorization.
22///
23/// Stats and metrics reject unsupported methods before authorization. Diagnostics
24/// authorizes every request to a registered path, including unsupported methods.
25/// Unregistered paths retain the router's not-found response.
26pub(super) fn routes(state: &RuntimeState, listener_address: SocketAddr) -> Router<RuntimeState> {
27    let policy = OperatorAccessPolicy::new(
28        state.config.diagnostics.auth_token.as_ref(),
29        listener_address,
30    );
31    let authorization = middleware::map_request_with_state(policy, authorize_operator);
32    let observation_routes = Router::new()
33        .route(
34            route::v1::STATS,
35            get(stats::rooms).route_layer(authorization.clone()),
36        )
37        .route(
38            route::METRICS,
39            get(metrics::scrape).route_layer(authorization.clone()),
40        );
41    let diagnostics_routes = Router::new()
42        .route(route::diagnostics::SUMMARY, get(diagnostics::summary))
43        .route(route::diagnostics::ROOMS, get(diagnostics::rooms))
44        .route(route::diagnostics::WORKERS, get(diagnostics::workers))
45        .route(route::diagnostics::ROOM, get(diagnostics::room_detail))
46        .route(route::diagnostics::ROOM_USERS, get(diagnostics::room_users))
47        .route(route::diagnostics::ROOM_USER, get(diagnostics::user_detail))
48        .route_layer(authorization);
49    observation_routes.merge(diagnostics_routes)
50}
51
52/// Passes authorized requests without consuming their bodies.
53///
54/// # Errors
55///
56/// Returns an HTTP [`Response`] with the policy rejection status.
57/// Unauthorized responses include the Bearer challenge required by RFC 9110.
58async fn authorize_operator(
59    State(policy): State<OperatorAccessPolicy>,
60    request: Request,
61) -> Result<Request, Response> {
62    policy.authorize(request.headers()).map_err(|status| {
63        let mut response = status.into_response();
64        if status == StatusCode::UNAUTHORIZED {
65            response.headers_mut().insert(
66                header::WWW_AUTHENTICATE,
67                HeaderValue::from_static("Bearer realm=\"o-sfu\""),
68            );
69        }
70        response
71    })?;
72    Ok(request)
73}