diff --git a/lldap_config.docker_template.toml b/lldap_config.docker_template.toml index d9f917c..05e63ef 100644 --- a/lldap_config.docker_template.toml +++ b/lldap_config.docker_template.toml @@ -33,6 +33,9 @@ ## The public URL of the server, for password reset links. #http_url = "http://localhost" +## The path to the front-end assets (relative to the working directory). +#assets_path = "./app" + ## Random secret for JWT signature. ## This secret should be random, and should be shared with application ## servers that need to consume the JWTs. diff --git a/server/src/infra/configuration.rs b/server/src/infra/configuration.rs index 3d68d4a..60c713d 100644 --- a/server/src/infra/configuration.rs +++ b/server/src/infra/configuration.rs @@ -22,6 +22,7 @@ use figment_file_provider_adapter::FileAdapter; use lldap_auth::opaque::{server::ServerSetup, KeyPair}; use secstr::SecUtf8; use serde::{Deserialize, Serialize}; +use std::path::PathBuf; use url::Url; #[derive( @@ -125,6 +126,8 @@ pub struct Configuration { // "***SECRET***". #[builder(default)] pub key_seed: Option, + #[builder(default = r#"PathBuf::from("./app")"#)] + pub assets_path: PathBuf, #[builder(default)] pub smtp_options: MailOptions, #[builder(default)] diff --git a/server/src/infra/tcp_server.rs b/server/src/infra/tcp_server.rs index d25c3ac..01272d0 100644 --- a/server/src/infra/tcp_server.rs +++ b/server/src/infra/tcp_server.rs @@ -21,11 +21,12 @@ use anyhow::{Context, Result}; use hmac::Hmac; use sha2::Sha512; use std::collections::HashSet; +use std::path::PathBuf; use std::sync::RwLock; -use tracing::info; +use tracing::{info, warn}; async fn index(data: web::Data>) -> actix_web::Result { - let mut file = std::fs::read_to_string(r"./app/index.html")?; + let mut file = std::fs::read_to_string(data.assets_path.join("index.html"))?; if data.server_url.path() != "/" { file = file.replace( @@ -80,7 +81,7 @@ pub(crate) fn error_to_http_response(error: TcpError) -> HttpResponse { async fn main_js_handler( data: web::Data>, ) -> actix_web::Result { - let mut file = std::fs::read_to_string(r"./app/static/main.js")?; + let mut file = std::fs::read_to_string(data.assets_path.join("static/main.js"))?; if data.server_url.path() != "/" { file = file.replace("/pkg/", format!("{}/pkg/", data.server_url.path()).as_str()); @@ -91,13 +92,20 @@ async fn main_js_handler( .insert_header((header::CONTENT_TYPE, "text/javascript"))) } -async fn wasm_handler() -> actix_web::Result { - Ok(actix_files::NamedFile::open_async("./app/pkg/lldap_app_bg.wasm").await?) +async fn wasm_handler( + data: web::Data>, +) -> actix_web::Result { + Ok( + actix_files::NamedFile::open_async(data.assets_path.join("pkg/lldap_app_bg.wasm.gz")) + .await?, + ) } -async fn wasm_handler_compressed() -> actix_web::Result { +async fn wasm_handler_compressed( + data: web::Data>, +) -> actix_web::Result { Ok( - actix_files::NamedFile::open_async("./app/pkg/lldap_app_bg.wasm.gz") + actix_files::NamedFile::open_async(data.assets_path.join("pkg/lldap_app_bg.wasm.gz")) .await? .customize() .insert_header(header::ContentEncoding::Gzip) @@ -111,6 +119,7 @@ fn http_config( jwt_secret: secstr::SecUtf8, jwt_blacklist: HashSet, server_url: url::Url, + assets_path: PathBuf, mail_options: MailOptions, ) where Backend: TcpBackendHandler + BackendHandler + LoginHandler + OpaqueHandler + Clone + 'static, @@ -121,6 +130,7 @@ fn http_config( jwt_key: hmac::Mac::new_from_slice(jwt_secret.unsecure().as_bytes()).unwrap(), jwt_blacklist: RwLock::new(jwt_blacklist), server_url, + assets_path: assets_path.clone(), mail_options, })) .route( @@ -138,16 +148,22 @@ fn http_config( .configure(super::graphql::api::configure_endpoint::), ) .service( - web::resource("/pkg/lldap_app_bg.wasm.gz").route(web::route().to(wasm_handler_compressed)), + web::resource("/pkg/lldap_app_bg.wasm.gz") + .route(web::route().to(wasm_handler_compressed::)), + ) + .service( + web::resource("/pkg/lldap_app_bg.wasm").route(web::route().to(wasm_handler::)), ) - .service(web::resource("/pkg/lldap_app_bg.wasm").route(web::route().to(wasm_handler))) .service(web::resource("/static/main.js").route(web::route().to(main_js_handler::))) // Serve the /pkg path with the compiled WASM app. - .service(Files::new("/pkg", "./app/pkg")) + .service(Files::new("/pkg", assets_path.join("pkg"))) // Serve static files - .service(Files::new("/static", "./app/static")) + .service(Files::new("/static", assets_path.join("static"))) // Serve static fonts - .service(Files::new("/static/fonts", "./app/static/fonts")) + .service(Files::new( + "/static/fonts", + assets_path.join("static/fonts"), + )) // Default to serve index.html for unknown routes, to support routing. .default_service(web::route().guard(guard::Get()).to(index::)); } @@ -157,6 +173,7 @@ pub(crate) struct AppState { pub jwt_key: Hmac, pub jwt_blacklist: RwLock>, pub server_url: url::Url, + pub assets_path: PathBuf, pub mail_options: MailOptions, } @@ -195,8 +212,12 @@ where .await .context("while getting the jwt blacklist")?; let server_url = config.http_url.0.clone(); + let assets_path = config.assets_path.clone(); let mail_options = config.smtp_options.clone(); let verbose = config.verbose; + if !assets_path.join("index.html").exists() { + warn!("Cannot find {}, please ensure that assets_path is set correctly and that the front-end files exist.", assets_path.to_string_lossy()) + } info!("Starting the API/web server on port {}", config.http_port); server_builder .bind( @@ -207,6 +228,7 @@ where let jwt_secret = jwt_secret.clone(); let jwt_blacklist = jwt_blacklist.clone(); let server_url = server_url.clone(); + let assets_path = assets_path.clone(); let mail_options = mail_options.clone(); HttpServiceBuilder::default() .finish(map_config( @@ -222,6 +244,7 @@ where jwt_secret, jwt_blacklist, server_url, + assets_path, mail_options, ) }),