resymo_agent/uplink/
http_server.rs

1use crate::{common::http, manager::Manager};
2use actix_web::{get, middleware::Logger, web, App, HttpResponse, Responder};
3use actix_web_extras::middleware::Condition;
4use actix_web_httpauth::{
5    extractors::{bearer, AuthenticationError},
6    middleware::HttpAuthentication,
7};
8use anyhow::bail;
9use std::{
10    net::{IpAddr, Ipv6Addr},
11    sync::Arc,
12};
13
14const DEFAULT_BIND_PORT: u16 = 4242;
15const DEFAULT_BIND_HOST: IpAddr = IpAddr::V6(Ipv6Addr::LOCALHOST);
16
17#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, schemars::JsonSchema)]
18#[serde(rename_all = "camelCase")]
19pub struct Options {
20    /// Remote access token
21    #[serde(default, skip_serializing_if = "Option::is_none")]
22    token: Option<String>,
23
24    /// Allow disabling the authentication
25    #[serde(default)]
26    disable_authentication: bool,
27
28    #[serde(flatten)]
29    http: http::Options,
30}
31
32#[get("/")]
33async fn index() -> impl Responder {
34    ""
35}
36
37#[get("/api/v1/collect")]
38async fn collect_all(manager: web::Data<Manager>) -> actix_web::Result<HttpResponse> {
39    Ok(HttpResponse::Ok().json(manager.collect_all().await?))
40}
41
42#[get("/api/v1/collect/{collector}")]
43async fn collect(
44    path: web::Path<String>,
45    manager: web::Data<Manager>,
46) -> actix_web::Result<HttpResponse> {
47    let collector = path.into_inner();
48
49    log::info!("Collecting: {collector}");
50
51    Ok(match manager.collect_one(&collector).await? {
52        Some(result) => HttpResponse::Ok().json(result),
53        None => HttpResponse::NotFound().finish(),
54    })
55}
56
57pub async fn run(options: Options, manager: Arc<Manager>) -> anyhow::Result<()> {
58    let manager = web::Data::from(manager);
59
60    let auth = options.token.map(|token| {
61        let token = Arc::new(token);
62        HttpAuthentication::bearer(move |req, credentials| {
63            let token = token.clone();
64            async move {
65                if credentials.token() == *token {
66                    Ok(req)
67                } else {
68                    let config = req
69                        .app_data::<bearer::Config>()
70                        .cloned()
71                        .unwrap_or_default()
72                        .scope("api");
73
74                    Err((AuthenticationError::from(config).into(), req))
75                }
76            }
77        })
78    });
79
80    if auth.is_none() {
81        if options.disable_authentication {
82            log::warn!("Running without access token. This is discouraged as it may compromise your system.");
83        } else {
84            bail!("Running without access token. This is discouraged as it may compromise your system. If you really want to do it, use --disable-authentication");
85        }
86    }
87
88    http::run_server(
89        options.http,
90        http::Defaults {
91            port: DEFAULT_BIND_PORT,
92            host: DEFAULT_BIND_HOST,
93        },
94        move || {
95            App::new()
96                .app_data(manager.clone())
97                .wrap(Condition::from_option(auth.clone()))
98                .wrap(Logger::default())
99                .service(index)
100                .service(collect)
101                .service(collect_all)
102        },
103    )
104    .await?;
105
106    Ok(())
107}