diff --git a/crates/cli/src/commands/server.rs b/crates/cli/src/commands/server.rs index fedc344a..65ce9ecc 100644 --- a/crates/cli/src/commands/server.rs +++ b/crates/cli/src/commands/server.rs @@ -27,7 +27,7 @@ use mas_email::Mailer; use mas_handlers::{AppState, MatrixHomeserver}; use mas_http::ServerLayer; use mas_policy::PolicyFactory; -use mas_router::UrlBuilder; +use mas_router::{Route, UrlBuilder}; use mas_storage::MIGRATOR; use mas_tasks::TaskQueue; use mas_templates::Templates; @@ -201,10 +201,16 @@ impl Options { .context("failed to load the policy")?; let policy_factory = Arc::new(policy_factory); + let url_builder = UrlBuilder::new(config.http.public_base.clone()); + // Load and compile the templates - let templates = Templates::load(config.templates.path.clone(), config.templates.builtin) - .await - .context("could not load templates")?; + let templates = Templates::load( + config.templates.path.clone(), + config.templates.builtin, + url_builder.clone(), + ) + .await + .context("could not load templates")?; let mailer = Mailer::new( &templates, @@ -213,8 +219,6 @@ impl Options { &config.email.reply_to, ); - let url_builder = UrlBuilder::new(config.http.public_base.clone()); - let static_files = mas_static_files::service(&config.http.web_root); let homeserver = MatrixHomeserver::new(config.matrix.homeserver.clone()); @@ -246,7 +250,7 @@ impl Options { }; let router = mas_handlers::router(state) - .fallback_service(static_files) + .nest(mas_router::StaticAsset::route(), static_files) .layer(ServerLayer::default()); info!("Listening on http://{}", listener.local_addr().unwrap()); diff --git a/crates/cli/src/commands/templates.rs b/crates/cli/src/commands/templates.rs index 9d71e6ff..c4639be7 100644 --- a/crates/cli/src/commands/templates.rs +++ b/crates/cli/src/commands/templates.rs @@ -15,7 +15,6 @@ use std::path::PathBuf; use clap::Parser; -use mas_config::TemplatesConfig; use mas_templates::Templates; #[derive(Parser, Debug)] @@ -58,11 +57,9 @@ impl Options { } SC::Check { path, skip_builtin } => { - let config = TemplatesConfig { - path: Some(path.to_string()), - builtin: !skip_builtin, - }; - let templates = Templates::load(config.path.clone(), config.builtin).await?; + let url_builder = mas_router::UrlBuilder::new("https://example.com/".parse()?); + let templates = + Templates::load(Some(path.into()), !skip_builtin, url_builder).await?; templates.check_render().await?; Ok(()) diff --git a/crates/handlers/src/lib.rs b/crates/handlers/src/lib.rs index 21991406..0a6967b6 100644 --- a/crates/handlers/src/lib.rs +++ b/crates/handlers/src/lib.rs @@ -280,7 +280,9 @@ where async fn test_state(pool: PgPool) -> Result, anyhow::Error> { use mas_email::MailTransport; - let templates = Templates::load(None, true).await?; + let url_builder = UrlBuilder::new("https://example.com/".parse()?); + + let templates = Templates::load(None, true, url_builder.clone()).await?; // TODO: add test keys to the store let key_store = Keystore::default(); @@ -291,8 +293,6 @@ async fn test_state(pool: PgPool) -> Result, anyhow::Error> { let mailbox = "server@example.com".parse()?; let mailer = Mailer::new(&templates, &transport, &mailbox, &mailbox); - let url_builder = UrlBuilder::new("https://example.com/".parse()?); - let homeserver = MatrixHomeserver::new("example.com".to_owned()); let policy_factory = PolicyFactory::load_default(serde_json::json!({})).await?; let policy_factory = Arc::new(policy_factory); diff --git a/crates/router/src/endpoints.rs b/crates/router/src/endpoints.rs index 46ac4406..ffdf0b34 100644 --- a/crates/router/src/endpoints.rs +++ b/crates/router/src/endpoints.rs @@ -522,3 +522,26 @@ impl Route for CompatLoginSsoComplete { format!("/complete-compat-sso/{}", self.id).into() } } + +/// `GET /assets` +pub struct StaticAsset { + path: String, +} + +impl StaticAsset { + #[must_use] + pub fn new(path: String) -> Self { + Self { path } + } +} + +impl Route for StaticAsset { + type Query = (); + fn route() -> &'static str { + "/assets" + } + + fn path(&self) -> std::borrow::Cow<'static, str> { + format!("/assets/{}", self.path).into() + } +} diff --git a/crates/router/src/url_builder.rs b/crates/router/src/url_builder.rs index 5453061c..ce2abd81 100644 --- a/crates/router/src/url_builder.rs +++ b/crates/router/src/url_builder.rs @@ -91,4 +91,10 @@ impl UrlBuilder { pub fn jwks_uri(&self) -> Url { self.url_for(&crate::endpoints::OAuth2Keys) } + + /// Static asset + #[must_use] + pub fn static_asset(&self, path: String) -> Url { + self.url_for(&crate::endpoints::StaticAsset::new(path)) + } } diff --git a/crates/templates/src/functions.rs b/crates/templates/src/functions.rs index 3d80f9b9..a82b07a3 100644 --- a/crates/templates/src/functions.rs +++ b/crates/templates/src/functions.rs @@ -16,14 +16,16 @@ use std::{collections::HashMap, str::FromStr}; +use mas_router::{Route, UrlBuilder}; use tera::{helpers::tests::number_args_allowed, Tera, Value}; use url::Url; -pub fn register(tera: &mut Tera) { +pub fn register(tera: &mut Tera, url_builder: UrlBuilder) { tera.register_tester("empty", self::tester_empty); tera.register_function("add_params_to_uri", function_add_params_to_uri); tera.register_function("merge", function_merge); tera.register_function("dict", function_dict); + tera.register_function("static_asset", make_static_asset(url_builder)); } fn tester_empty(value: Option<&Value>, params: &[Value]) -> Result { @@ -115,3 +117,26 @@ fn function_dict(params: &HashMap) -> Result let ret = params.clone().into_iter().collect(); Ok(Value::Object(ret)) } + +fn make_static_asset(url_builder: UrlBuilder) -> impl tera::Function { + Box::new( + move |args: &HashMap| -> Result { + if let Some(path) = args.get("path").and_then(Value::as_str) { + let absolute = args + .get("absolute") + .and_then(Value::as_bool) + .unwrap_or(false); + let path = path.to_owned(); + let url = if absolute { + url_builder.static_asset(path).into() + } else { + let destination = mas_router::StaticAsset::new(path); + destination.relative_url().into_owned() + }; + Ok(Value::String(url)) + } else { + Err(tera::Error::msg("Invalid parameter 'path'")) + } + }, + ) +} diff --git a/crates/templates/src/lib.rs b/crates/templates/src/lib.rs index 3261c55a..c1deade1 100644 --- a/crates/templates/src/lib.rs +++ b/crates/templates/src/lib.rs @@ -34,6 +34,7 @@ use std::{ use anyhow::{bail, Context as _}; use mas_data_model::StorageBackend; +use mas_router::UrlBuilder; use serde::Serialize; use tera::{Context, Error as TeraError, Tera}; use thiserror::Error; @@ -62,6 +63,7 @@ pub use self::{ #[derive(Debug, Clone)] pub struct Templates { tera: Arc>, + url_builder: UrlBuilder, path: Option, builtin: bool, } @@ -134,16 +136,25 @@ impl Templates { } /// Load the templates from the given config - pub async fn load(path: Option, builtin: bool) -> Result { - let tera = Self::load_(path.as_deref(), builtin).await?; + pub async fn load( + path: Option, + builtin: bool, + url_builder: UrlBuilder, + ) -> Result { + let tera = Self::load_(path.as_deref(), builtin, url_builder.clone()).await?; Ok(Self { tera: Arc::new(RwLock::new(tera)), path, + url_builder, builtin, }) } - async fn load_(path: Option<&str>, builtin: bool) -> Result { + async fn load_( + path: Option<&str>, + builtin: bool, + url_builder: UrlBuilder, + ) -> Result { let mut teras = Vec::new(); let roots = Self::roots(path, builtin).await; @@ -183,7 +194,7 @@ impl Templates { tera.build_inheritance_chains()?; tera.check_macro_files()?; - self::functions::register(&mut tera); + self::functions::register(&mut tera, url_builder); let loaded: HashSet<_> = tera.get_template_names().collect(); let needed: HashSet<_> = TEMPLATES.into_iter().map(|(name, _)| name).collect(); @@ -202,7 +213,8 @@ impl Templates { /// Reload the templates on disk pub async fn reload(&self) -> anyhow::Result<()> { // Prepare the new Tera instance - let new_tera = Self::load_(self.path.as_deref(), self.builtin).await?; + let new_tera = + Self::load_(self.path.as_deref(), self.builtin, self.url_builder.clone()).await?; // Swap it *self.tera.write().await = new_tera; @@ -378,7 +390,8 @@ mod tests { #[tokio::test] async fn check_builtin_templates() { - let templates = Templates::load(None, true).await.unwrap(); + let url_builder = UrlBuilder::new("https://example.com/".parse().unwrap()); + let templates = Templates::load(None, true, url_builder).await.unwrap(); templates.check_render().await.unwrap(); } } diff --git a/crates/templates/src/res/base.html b/crates/templates/src/res/base.html index 376b1092..72635e50 100644 --- a/crates/templates/src/res/base.html +++ b/crates/templates/src/res/base.html @@ -27,7 +27,7 @@ limitations under the License. {% block title %}matrix-authentication-service{% endblock title %} - + {% block content %}{% endblock content %}