From 830f47b61fe0ddd698f7992cd29d7a4b53cab123 Mon Sep 17 00:00:00 2001 From: Pierre HUBERT Date: Mon, 3 Nov 2025 19:13:42 +0100 Subject: [PATCH] Add server config route --- matrixgw_backend/Cargo.lock | 12 +++ matrixgw_backend/Cargo.toml | 3 +- matrixgw_backend/src/app_config.rs | 24 ++++++ matrixgw_backend/src/controllers/mod.rs | 1 + .../src/controllers/server_controller.rs | 74 +++++++++++++++++++ matrixgw_backend/src/lib.rs | 1 + matrixgw_backend/src/main.rs | 33 +++++++-- 7 files changed, 141 insertions(+), 7 deletions(-) create mode 100644 matrixgw_backend/src/controllers/mod.rs create mode 100644 matrixgw_backend/src/controllers/server_controller.rs diff --git a/matrixgw_backend/Cargo.lock b/matrixgw_backend/Cargo.lock index 079c0e7..a6569f5 100644 --- a/matrixgw_backend/Cargo.lock +++ b/matrixgw_backend/Cargo.lock @@ -68,6 +68,17 @@ dependencies = [ "syn", ] +[[package]] +name = "actix-remote-ip" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7629b357d4705cf3f1e31f989f48ecd56027112f7d52dcf06dd96ee197065f8e" +dependencies = [ + "actix-web", + "futures-util", + "log", +] + [[package]] name = "actix-router" version = "0.5.3" @@ -1405,6 +1416,7 @@ checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" name = "matrixgw_backend" version = "0.1.0" dependencies = [ + "actix-remote-ip", "actix-session", "actix-web", "anyhow", diff --git a/matrixgw_backend/Cargo.toml b/matrixgw_backend/Cargo.toml index c282d75..9ae5242 100644 --- a/matrixgw_backend/Cargo.toml +++ b/matrixgw_backend/Cargo.toml @@ -13,4 +13,5 @@ serde = { version = "1.0.228", features = ["derive"] } rust-s3 = { version = "0.37.0", features = ["tokio"] } tokio = { version = "1.48.0", features = ["full"] } actix-web = "4.11.0" -actix-session = { version = "0.11.0", features = ["redis-session"] } \ No newline at end of file +actix-session = { version = "0.11.0", features = ["redis-session"] } +actix-remote-ip = "0.1.0" \ No newline at end of file diff --git a/matrixgw_backend/src/app_config.rs b/matrixgw_backend/src/app_config.rs index a1271d7..7552544 100644 --- a/matrixgw_backend/src/app_config.rs +++ b/matrixgw_backend/src/app_config.rs @@ -18,6 +18,11 @@ pub struct AppConfig { #[clap(short, long, env)] pub proxy_ip: Option, + /// Unsecure : for development, bypass authentication, using the account with the given + /// email address by default + #[clap(long, env)] + unsecure_auto_login_email: Option, + /// Secret key, used to secure some resources. Must be randomly generated #[clap(short = 'S', long, env, default_value = "")] secret: String, @@ -54,6 +59,10 @@ pub struct AppConfig { )] pub oidc_configuration_url: String, + /// OpenID provider name + #[arg(long, env, default_value = "3rd party provider")] + pub oidc_provider_name: String, + /// OpenID client ID #[arg(long, env, default_value = "foo")] pub oidc_client_id: String, @@ -103,6 +112,14 @@ impl AppConfig { &ARGS } + /// Get auto login email (if not empty) + pub fn unsecure_auto_login_email(&self) -> Option<&str> { + match self.unsecure_auto_login_email.as_deref() { + None | Some("") => None, + s => s, + } + } + /// Get app secret pub fn secret(&self) -> &str { let mut secret = self.secret.as_str(); @@ -118,6 +135,11 @@ impl AppConfig { secret } + /// Check if auth is disabled + pub fn is_auth_disabled(&self) -> bool { + self.unsecure_auto_login_email().is_some() + } + /// Get Redis connection configuration pub fn redis_connection_string(&self) -> String { format!( @@ -136,6 +158,7 @@ impl AppConfig { client_id: self.oidc_client_id.as_str(), client_secret: self.oidc_client_secret.as_str(), configuration_url: self.oidc_configuration_url.as_str(), + name: self.oidc_provider_name.as_str(), redirect_url: self .oidc_redirect_url .replace("APP_ORIGIN", &self.website_origin), @@ -169,6 +192,7 @@ impl AppConfig { #[derive(Debug, Clone, serde::Serialize)] pub struct OIDCProvider<'a> { + pub name: &'a str, pub client_id: &'a str, pub client_secret: &'a str, pub configuration_url: &'a str, diff --git a/matrixgw_backend/src/controllers/mod.rs b/matrixgw_backend/src/controllers/mod.rs new file mode 100644 index 0000000..98a0949 --- /dev/null +++ b/matrixgw_backend/src/controllers/mod.rs @@ -0,0 +1 @@ +pub mod server_controller; diff --git a/matrixgw_backend/src/controllers/server_controller.rs b/matrixgw_backend/src/controllers/server_controller.rs new file mode 100644 index 0000000..22683ee --- /dev/null +++ b/matrixgw_backend/src/controllers/server_controller.rs @@ -0,0 +1,74 @@ +use crate::app_config::AppConfig; +use actix_web::HttpResponse; + +/// Serve robots.txt (disallow ranking) +pub async fn robots_txt() -> HttpResponse { + HttpResponse::Ok() + .content_type("text/plain") + .body("User-agent: *\nDisallow: /\n") +} + +#[derive(serde::Serialize)] +pub struct LenConstraints { + min: usize, + max: usize, +} + +impl LenConstraints { + pub fn new(min: usize, max: usize) -> Self { + Self { min, max } + } + pub fn not_empty(max: usize) -> Self { + Self { min: 1, max } + } + pub fn max_only(max: usize) -> Self { + Self { min: 0, max } + } + + pub fn check_str(&self, s: &str) -> bool { + s.len() >= self.min && s.len() <= self.max + } + + pub fn check_u32(&self, v: u32) -> bool { + v >= self.min as u32 && v <= self.max as u32 + } +} + +#[derive(serde::Serialize)] +pub struct ServerConstraints { + pub token_name: LenConstraints, + pub token_ip_net: LenConstraints, + pub token_max_inactivity: LenConstraints, +} + +impl Default for ServerConstraints { + fn default() -> Self { + Self { + token_name: LenConstraints::new(5, 255), + token_ip_net: LenConstraints::max_only(44), + token_max_inactivity: LenConstraints::new(3600, 3600 * 24 * 365), + } + } +} + +#[derive(serde::Serialize)] +struct ServerConfig { + auth_disabled: bool, + oidc_provider_name: &'static str, + constraints: ServerConstraints, +} + +impl Default for ServerConfig { + fn default() -> Self { + Self { + auth_disabled: AppConfig::get().is_auth_disabled(), + oidc_provider_name: AppConfig::get().openid_provider().name, + constraints: Default::default(), + } + } +} + +/// Get server static configuration +pub async fn config() -> HttpResponse { + HttpResponse::Ok().json(ServerConfig::default()) +} diff --git a/matrixgw_backend/src/lib.rs b/matrixgw_backend/src/lib.rs index 62a6f82..2483671 100644 --- a/matrixgw_backend/src/lib.rs +++ b/matrixgw_backend/src/lib.rs @@ -1 +1,2 @@ pub mod app_config; +pub mod controllers; diff --git a/matrixgw_backend/src/main.rs b/matrixgw_backend/src/main.rs index 8bc6c8c..3d6132f 100644 --- a/matrixgw_backend/src/main.rs +++ b/matrixgw_backend/src/main.rs @@ -1,7 +1,11 @@ +use actix_remote_ip::RemoteIPConfig; +use actix_session::SessionMiddleware; +use actix_session::config::SessionLifecycle; use actix_session::storage::RedisSessionStore; use actix_web::cookie::Key; -use actix_web::{App, HttpServer}; +use actix_web::{App, HttpServer, web}; use matrixgw_backend::app_config::AppConfig; +use matrixgw_backend::controllers::server_controller; #[tokio::main] async fn main() -> std::io::Result<()> { @@ -19,9 +23,26 @@ async fn main() -> std::io::Result<()> { AppConfig::get().website_origin ); - HttpServer::new(move || App::new()) - .workers(4) - .bind(&AppConfig::get().listen_address)? - .run() - .await + HttpServer::new(move || { + App::new() + .wrap( + SessionMiddleware::builder(redis_store.clone(), secret_key.clone()) + .cookie_name("matrixgw-session".to_string()) + .session_lifecycle(SessionLifecycle::BrowserSession(Default::default())) + .build(), + ) + .app_data(web::Data::new(RemoteIPConfig { + proxy: AppConfig::get().proxy_ip.clone(), + })) + // Server controller + .route("/robots.txt", web::get().to(server_controller::robots_txt)) + .route( + "/api/server/config", + web::get().to(server_controller::config), + ) + }) + .workers(4) + .bind(&AppConfig::get().listen_address)? + .run() + .await }