diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 8f1403788..f290e7796 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -215,7 +215,7 @@ jobs: uses: actions/checkout@v4.2.2 - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@1.86.0 + uses: dtolnay/rust-toolchain@1.87.0 with: components: clippy diff --git a/Dockerfile b/Dockerfile index 354f5fada..1c1c66b56 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,7 +12,7 @@ # The Debian version and version name must be in sync ARG DEBIAN_VERSION=12 ARG DEBIAN_VERSION_NAME=bookworm -ARG RUSTC_VERSION=1.86.0 +ARG RUSTC_VERSION=1.87.0 ARG NODEJS_VERSION=20.15.0 ARG OPA_VERSION=1.1.0 ARG CARGO_AUDITABLE_VERSION=0.6.6 diff --git a/crates/cli/src/commands/config.rs b/crates/cli/src/commands/config.rs index f253f6cd5..73c100e2c 100644 --- a/crates/cli/src/commands/config.rs +++ b/crates/cli/src/commands/config.rs @@ -72,7 +72,7 @@ impl Options { SC::Dump { output } => { let _span = info_span!("cli.config.dump").entered(); - let config = RootConfig::extract(figment)?; + let config = RootConfig::extract(figment).map_err(anyhow::Error::from_boxed)?; let config = serde_yaml::to_string(&config)?; if let Some(output) = output { @@ -88,7 +88,7 @@ impl Options { SC::Check => { let _span = info_span!("cli.config.check").entered(); - let _config = RootConfig::extract(figment)?; + let _config = RootConfig::extract(figment).map_err(anyhow::Error::from_boxed)?; info!("Configuration file looks good"); } @@ -105,7 +105,8 @@ impl Options { if !synapse_config.is_empty() { info!("Adjusting MAS config to match Synapse config from {synapse_config:?}"); - let synapse_config = syn2mas::synapse_config::Config::load(&synapse_config)?; + let synapse_config = syn2mas::synapse_config::Config::load(&synapse_config) + .map_err(anyhow::Error::from_boxed)?; config = synapse_config.adjust_mas_config(config, &mut rng, clock.now()); } @@ -121,7 +122,7 @@ impl Options { } SC::Sync { prune, dry_run } => { - let config = SyncConfig::extract(figment)?; + let config = SyncConfig::extract(figment).map_err(anyhow::Error::from_boxed)?; let clock = SystemClock::default(); let encrypter = config.secrets.encrypter().await?; diff --git a/crates/cli/src/commands/database.rs b/crates/cli/src/commands/database.rs index 740c5c53c..519536fff 100644 --- a/crates/cli/src/commands/database.rs +++ b/crates/cli/src/commands/database.rs @@ -30,7 +30,8 @@ enum Subcommand { impl Options { pub async fn run(self, figment: &Figment) -> anyhow::Result { let _span = info_span!("cli.database.migrate").entered(); - let config = DatabaseConfig::extract_or_default(figment)?; + let config = + DatabaseConfig::extract_or_default(figment).map_err(anyhow::Error::from_boxed)?; let mut conn = database_connection_from_config(&config).await?; // Run pending migrations diff --git a/crates/cli/src/commands/debug.rs b/crates/cli/src/commands/debug.rs index b57034b42..bb87c5e81 100644 --- a/crates/cli/src/commands/debug.rs +++ b/crates/cli/src/commands/debug.rs @@ -41,13 +41,16 @@ impl Options { match self.subcommand { SC::Policy { with_dynamic_data } => { let _span = info_span!("cli.debug.policy").entered(); - let config = PolicyConfig::extract_or_default(figment)?; - let matrix_config = MatrixConfig::extract(figment)?; + let config = + PolicyConfig::extract_or_default(figment).map_err(anyhow::Error::from_boxed)?; + let matrix_config = + MatrixConfig::extract(figment).map_err(anyhow::Error::from_boxed)?; info!("Loading and compiling the policy module"); let policy_factory = policy_factory_from_config(&config, &matrix_config).await?; if with_dynamic_data { - let database_config = DatabaseConfig::extract(figment)?; + let database_config = + DatabaseConfig::extract(figment).map_err(anyhow::Error::from_boxed)?; let pool = database_pool_from_config(&database_config).await?; let repository_factory = PgRepositoryFactory::new(pool.clone()); load_policy_factory_dynamic_data(&policy_factory, &repository_factory).await?; diff --git a/crates/cli/src/commands/doctor.rs b/crates/cli/src/commands/doctor.rs index 521ade442..c7eb2768a 100644 --- a/crates/cli/src/commands/doctor.rs +++ b/crates/cli/src/commands/doctor.rs @@ -33,7 +33,7 @@ impl Options { "💡 Running diagnostics, make sure that both MAS and Synapse are running, and that MAS is using the same configuration files as this tool." ); - let config = RootConfig::extract(figment)?; + let config = RootConfig::extract(figment).map_err(anyhow::Error::from_boxed)?; // We'll need an HTTP client let http_client = mas_http::reqwest_client(); diff --git a/crates/cli/src/commands/manage.rs b/crates/cli/src/commands/manage.rs index 12560a9ac..08551c101 100644 --- a/crates/cli/src/commands/manage.rs +++ b/crates/cli/src/commands/manage.rs @@ -223,8 +223,10 @@ impl Options { let _span = info_span!("cli.manage.set_password", user.username = %username).entered(); - let database_config = DatabaseConfig::extract_or_default(figment)?; - let passwords_config = PasswordsConfig::extract_or_default(figment)?; + let database_config = DatabaseConfig::extract_or_default(figment) + .map_err(anyhow::Error::from_boxed)?; + let passwords_config = PasswordsConfig::extract_or_default(figment) + .map_err(anyhow::Error::from_boxed)?; let mut conn = database_connection_from_config(&database_config).await?; let password_manager = password_manager_from_config(&passwords_config).await?; @@ -264,7 +266,8 @@ impl Options { ) .entered(); - let database_config = DatabaseConfig::extract_or_default(figment)?; + let database_config = DatabaseConfig::extract_or_default(figment) + .map_err(anyhow::Error::from_boxed)?; let mut conn = database_connection_from_config(&database_config).await?; let txn = conn.begin().await?; let mut repo = PgRepository::from_conn(txn); @@ -318,7 +321,8 @@ impl Options { admin, device_id, } => { - let database_config = DatabaseConfig::extract_or_default(figment)?; + let database_config = DatabaseConfig::extract_or_default(figment) + .map_err(anyhow::Error::from_boxed)?; let mut conn = database_connection_from_config(&database_config).await?; let txn = conn.begin().await?; let mut repo = PgRepository::from_conn(txn); @@ -376,7 +380,8 @@ impl Options { (Some(_), true) => unreachable!(), // This should be handled by the clap group }; - let database_config = DatabaseConfig::extract_or_default(figment)?; + let database_config = DatabaseConfig::extract_or_default(figment) + .map_err(anyhow::Error::from_boxed)?; let mut conn = database_connection_from_config(&database_config).await?; let txn = conn.begin().await?; let mut repo = PgRepository::from_conn(txn); @@ -403,7 +408,8 @@ impl Options { SC::ProvisionAllUsers => { let _span = info_span!("cli.manage.provision_all_users").entered(); - let database_config = DatabaseConfig::extract_or_default(figment)?; + let database_config = DatabaseConfig::extract_or_default(figment) + .map_err(anyhow::Error::from_boxed)?; let mut conn = database_connection_from_config(&database_config).await?; let mut txn = conn.begin().await?; @@ -429,7 +435,8 @@ impl Options { SC::KillSessions { username, dry_run } => { let _span = info_span!("cli.manage.kill_sessions", user.username = username).entered(); - let database_config = DatabaseConfig::extract_or_default(figment)?; + let database_config = DatabaseConfig::extract_or_default(figment) + .map_err(anyhow::Error::from_boxed)?; let mut conn = database_connection_from_config(&database_config).await?; let txn = conn.begin().await?; let mut repo = PgRepository::from_conn(txn); @@ -501,7 +508,8 @@ impl Options { deactivate, } => { let _span = info_span!("cli.manage.lock_user", user.username = username).entered(); - let config = DatabaseConfig::extract_or_default(figment)?; + let config = DatabaseConfig::extract_or_default(figment) + .map_err(anyhow::Error::from_boxed)?; let mut conn = database_connection_from_config(&config).await?; let txn = conn.begin().await?; let mut repo = PgRepository::from_conn(txn); @@ -537,7 +545,8 @@ impl Options { } => { let _span = info_span!("cli.manage.unlock_user", user.username = username).entered(); - let config = DatabaseConfig::extract_or_default(figment)?; + let config = DatabaseConfig::extract_or_default(figment) + .map_err(anyhow::Error::from_boxed)?; let mut conn = database_connection_from_config(&config).await?; let txn = conn.begin().await?; let mut repo = PgRepository::from_conn(txn); @@ -574,9 +583,12 @@ impl Options { ignore_password_complexity, } => { let http_client = mas_http::reqwest_client(); - let password_config = PasswordsConfig::extract_or_default(figment)?; - let database_config = DatabaseConfig::extract_or_default(figment)?; - let matrix_config = MatrixConfig::extract(figment)?; + let password_config = PasswordsConfig::extract_or_default(figment) + .map_err(anyhow::Error::from_boxed)?; + let database_config = DatabaseConfig::extract_or_default(figment) + .map_err(anyhow::Error::from_boxed)?; + let matrix_config = + MatrixConfig::extract(figment).map_err(anyhow::Error::from_boxed)?; let password_manager = password_manager_from_config(&password_config).await?; let homeserver = homeserver_connection_from_config(&matrix_config, http_client); diff --git a/crates/cli/src/commands/server.rs b/crates/cli/src/commands/server.rs index c42e99da4..e7c5b0bc8 100644 --- a/crates/cli/src/commands/server.rs +++ b/crates/cli/src/commands/server.rs @@ -59,7 +59,7 @@ impl Options { pub async fn run(self, figment: &Figment) -> anyhow::Result { let span = info_span!("cli.run.init").entered(); let mut shutdown = LifecycleManager::new()?; - let config = AppConfig::extract(figment)?; + let config = AppConfig::extract(figment).map_err(anyhow::Error::from_boxed)?; info!(version = crate::VERSION, "Starting up"); @@ -101,8 +101,10 @@ impl Options { } else { // Sync the configuration with the database let mut conn = pool.acquire().await?; - let clients_config = ClientsConfig::extract_or_default(figment)?; - let upstream_oauth2_config = UpstreamOAuth2Config::extract_or_default(figment)?; + let clients_config = + ClientsConfig::extract_or_default(figment).map_err(anyhow::Error::from_boxed)?; + let upstream_oauth2_config = UpstreamOAuth2Config::extract_or_default(figment) + .map_err(anyhow::Error::from_boxed)?; crate::sync::config_sync( upstream_oauth2_config, diff --git a/crates/cli/src/commands/syn2mas.rs b/crates/cli/src/commands/syn2mas.rs index 741e4f976..fa768bf9d 100644 --- a/crates/cli/src/commands/syn2mas.rs +++ b/crates/cli/src/commands/syn2mas.rs @@ -96,6 +96,7 @@ impl Options { } let synapse_config = synapse_config::Config::load(&self.synapse_configuration_files) + .map_err(anyhow::Error::from_boxed) .context("Failed to load Synapse configuration")?; // Establish a connection to Synapse's Postgres database @@ -111,7 +112,8 @@ impl Options { .await .context("could not connect to Synapse Postgres database")?; - let config = DatabaseConfig::extract_or_default(figment)?; + let config = + DatabaseConfig::extract_or_default(figment).map_err(anyhow::Error::from_boxed)?; let mut mas_connection = database_connection_from_config_with_options( &config, @@ -131,7 +133,7 @@ impl Options { // First perform a config sync // This is crucial to ensure we register upstream OAuth providers // in the MAS database - let config = SyncConfig::extract(figment)?; + let config = SyncConfig::extract(figment).map_err(anyhow::Error::from_boxed)?; let clock = SystemClock::default(); let encrypter = config.secrets.encrypter().await?; @@ -213,7 +215,8 @@ impl Options { Subcommand::Migrate { dry_run } => { let provider_id_mappings: HashMap = { - let mas_oauth2 = UpstreamOAuth2Config::extract_or_default(figment)?; + let mas_oauth2 = UpstreamOAuth2Config::extract_or_default(figment) + .map_err(anyhow::Error::from_boxed)?; mas_oauth2 .providers @@ -252,7 +255,8 @@ impl Options { let occasional_progress_logger_task = tokio::spawn(occasional_progress_logger(progress.clone())); - let mas_matrix = MatrixConfig::extract(figment)?; + let mas_matrix = + MatrixConfig::extract(figment).map_err(anyhow::Error::from_boxed)?; syn2mas::migrate( reader, writer, diff --git a/crates/cli/src/commands/templates.rs b/crates/cli/src/commands/templates.rs index 6a6da318a..011c5d268 100644 --- a/crates/cli/src/commands/templates.rs +++ b/crates/cli/src/commands/templates.rs @@ -37,13 +37,20 @@ impl Options { SC::Check => { let _span = info_span!("cli.templates.check").entered(); - let template_config = TemplatesConfig::extract_or_default(figment)?; - let branding_config = BrandingConfig::extract_or_default(figment)?; - let matrix_config = MatrixConfig::extract(figment)?; - let experimental_config = ExperimentalConfig::extract_or_default(figment)?; - let password_config = PasswordsConfig::extract_or_default(figment)?; - let account_config = AccountConfig::extract_or_default(figment)?; - let captcha_config = CaptchaConfig::extract_or_default(figment)?; + let template_config = TemplatesConfig::extract_or_default(figment) + .map_err(anyhow::Error::from_boxed)?; + let branding_config = BrandingConfig::extract_or_default(figment) + .map_err(anyhow::Error::from_boxed)?; + let matrix_config = + MatrixConfig::extract(figment).map_err(anyhow::Error::from_boxed)?; + let experimental_config = ExperimentalConfig::extract_or_default(figment) + .map_err(anyhow::Error::from_boxed)?; + let password_config = PasswordsConfig::extract_or_default(figment) + .map_err(anyhow::Error::from_boxed)?; + let account_config = AccountConfig::extract_or_default(figment) + .map_err(anyhow::Error::from_boxed)?; + let captcha_config = CaptchaConfig::extract_or_default(figment) + .map_err(anyhow::Error::from_boxed)?; let clock = SystemClock::default(); // XXX: we should disallow SeedableRng::from_entropy diff --git a/crates/cli/src/commands/worker.rs b/crates/cli/src/commands/worker.rs index c4e162370..bd15e3b1f 100644 --- a/crates/cli/src/commands/worker.rs +++ b/crates/cli/src/commands/worker.rs @@ -29,7 +29,7 @@ impl Options { pub async fn run(self, figment: &Figment) -> anyhow::Result { let shutdown = LifecycleManager::new()?; let span = info_span!("cli.worker.init").entered(); - let config = AppConfig::extract(figment)?; + let config = AppConfig::extract(figment).map_err(anyhow::Error::from_boxed)?; // Connect to the database info!("Connecting to the database"); diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 59279589b..7cf646564 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -115,8 +115,9 @@ async fn try_main() -> anyhow::Result { // Load the base configuration files let figment = opts.figment(); - let telemetry_config = - TelemetryConfig::extract_or_default(&figment).context("Failed to load telemetry config")?; + let telemetry_config = TelemetryConfig::extract_or_default(&figment) + .map_err(anyhow::Error::from_boxed) + .context("Failed to load telemetry config")?; // Setup Sentry let sentry = sentry::init(( diff --git a/crates/config/src/sections/captcha.rs b/crates/config/src/sections/captcha.rs index 264bb47b1..962d1f342 100644 --- a/crates/config/src/sections/captcha.rs +++ b/crates/config/src/sections/captcha.rs @@ -51,7 +51,10 @@ impl CaptchaConfig { impl ConfigurationSection for CaptchaConfig { const PATH: Option<&'static str> = Some("captcha"); - fn validate(&self, figment: &figment::Figment) -> Result<(), figment::Error> { + fn validate( + &self, + figment: &figment::Figment, + ) -> Result<(), Box> { let metadata = figment.find_metadata(Self::PATH.unwrap()); let error_on_field = |mut error: figment::error::Error, field: &'static str| { @@ -67,11 +70,11 @@ impl ConfigurationSection for CaptchaConfig { if let Some(CaptchaServiceKind::RecaptchaV2) = self.service { if self.site_key.is_none() { - return Err(missing_field("site_key")); + return Err(missing_field("site_key").into()); } if self.secret_key.is_none() { - return Err(missing_field("secret_key")); + return Err(missing_field("secret_key").into()); } } diff --git a/crates/config/src/sections/clients.rs b/crates/config/src/sections/clients.rs index fdec32f47..5d2b0d453 100644 --- a/crates/config/src/sections/clients.rs +++ b/crates/config/src/sections/clients.rs @@ -6,7 +6,6 @@ use std::ops::Deref; -use figment::Figment; use mas_iana::oauth::OAuthClientAuthenticationMethod; use mas_jose::jwk::PublicJsonWebKeySet; use schemars::JsonSchema; @@ -104,7 +103,7 @@ pub struct ClientConfig { } impl ClientConfig { - fn validate(&self) -> Result<(), figment::error::Error> { + fn validate(&self) -> Result<(), Box> { let auth_method = self.client_auth_method; match self.client_auth_method { ClientAuthMethodConfig::PrivateKeyJwt => { @@ -112,20 +111,20 @@ impl ClientConfig { let error = figment::error::Error::custom( "jwks or jwks_uri is required for private_key_jwt", ); - return Err(error.with_path("client_auth_method")); + return Err(Box::new(error.with_path("client_auth_method"))); } if self.jwks.is_some() && self.jwks_uri.is_some() { let error = figment::error::Error::custom("jwks and jwks_uri are mutually exclusive"); - return Err(error.with_path("jwks")); + return Err(Box::new(error.with_path("jwks"))); } if self.client_secret.is_some() { let error = figment::error::Error::custom( "client_secret is not allowed with private_key_jwt", ); - return Err(error.with_path("client_secret")); + return Err(Box::new(error.with_path("client_secret"))); } } @@ -136,21 +135,21 @@ impl ClientConfig { let error = figment::error::Error::custom(format!( "client_secret is required for {auth_method}" )); - return Err(error.with_path("client_auth_method")); + return Err(Box::new(error.with_path("client_auth_method"))); } if self.jwks.is_some() { let error = figment::error::Error::custom(format!( "jwks is not allowed with {auth_method}" )); - return Err(error.with_path("jwks")); + return Err(Box::new(error.with_path("jwks"))); } if self.jwks_uri.is_some() { let error = figment::error::Error::custom(format!( "jwks_uri is not allowed with {auth_method}" )); - return Err(error.with_path("jwks_uri")); + return Err(Box::new(error.with_path("jwks_uri"))); } } @@ -159,21 +158,21 @@ impl ClientConfig { let error = figment::error::Error::custom( "client_secret is not allowed with none authentication method", ); - return Err(error.with_path("client_secret")); + return Err(Box::new(error.with_path("client_secret"))); } if self.jwks.is_some() { let error = figment::error::Error::custom( "jwks is not allowed with none authentication method", ); - return Err(error); + return Err(Box::new(error)); } if self.jwks_uri.is_some() { let error = figment::error::Error::custom( "jwks_uri is not allowed with none authentication method", ); - return Err(error); + return Err(Box::new(error)); } } } @@ -232,7 +231,10 @@ impl IntoIterator for ClientsConfig { impl ConfigurationSection for ClientsConfig { const PATH: Option<&'static str> = Some("clients"); - fn validate(&self, figment: &Figment) -> Result<(), figment::error::Error> { + fn validate( + &self, + figment: &figment::Figment, + ) -> Result<(), Box> { for (index, client) in self.0.iter().enumerate() { client.validate().map_err(|mut err| { // Save the error location information in the error diff --git a/crates/config/src/sections/database.rs b/crates/config/src/sections/database.rs index 399053d41..4830a4016 100644 --- a/crates/config/src/sections/database.rs +++ b/crates/config/src/sections/database.rs @@ -222,13 +222,16 @@ pub struct DatabaseConfig { impl ConfigurationSection for DatabaseConfig { const PATH: Option<&'static str> = Some("database"); - fn validate(&self, figment: &figment::Figment) -> Result<(), figment::error::Error> { + fn validate( + &self, + figment: &figment::Figment, + ) -> Result<(), Box> { let metadata = figment.find_metadata(Self::PATH.unwrap()); let annotate = |mut error: figment::Error| { error.metadata = metadata.cloned(); error.profile = Some(figment::Profile::Default); error.path = vec![Self::PATH.unwrap().to_owned()]; - Err(error) + error }; // Check that the user did not specify both `uri` and the split options at the @@ -241,37 +244,41 @@ impl ConfigurationSection for DatabaseConfig { || self.database.is_some(); if self.uri.is_some() && has_split_options { - return annotate(figment::error::Error::from( + return Err(annotate(figment::error::Error::from( "uri must not be specified if host, port, socket, username, password, or database are specified".to_owned(), - )); + )).into()); } if self.ssl_ca.is_some() && self.ssl_ca_file.is_some() { - return annotate(figment::error::Error::from( + return Err(annotate(figment::error::Error::from( "ssl_ca must not be specified if ssl_ca_file is specified".to_owned(), - )); + )) + .into()); } if self.ssl_certificate.is_some() && self.ssl_certificate_file.is_some() { - return annotate(figment::error::Error::from( + return Err(annotate(figment::error::Error::from( "ssl_certificate must not be specified if ssl_certificate_file is specified" .to_owned(), - )); + )) + .into()); } if self.ssl_key.is_some() && self.ssl_key_file.is_some() { - return annotate(figment::error::Error::from( + return Err(annotate(figment::error::Error::from( "ssl_key must not be specified if ssl_key_file is specified".to_owned(), - )); + )) + .into()); } if (self.ssl_key.is_some() || self.ssl_key_file.is_some()) ^ (self.ssl_certificate.is_some() || self.ssl_certificate_file.is_some()) { - return annotate(figment::error::Error::from( + return Err(annotate(figment::error::Error::from( "both a ssl_certificate and a ssl_key must be set at the same time or none of them" .to_owned(), - )); + )) + .into()); } Ok(()) diff --git a/crates/config/src/sections/email.rs b/crates/config/src/sections/email.rs index 8a809bed3..3df0c99db 100644 --- a/crates/config/src/sections/email.rs +++ b/crates/config/src/sections/email.rs @@ -175,7 +175,10 @@ impl Default for EmailConfig { impl ConfigurationSection for EmailConfig { const PATH: Option<&'static str> = Some("email"); - fn validate(&self, figment: &figment::Figment) -> Result<(), figment::error::Error> { + fn validate( + &self, + figment: &figment::Figment, + ) -> Result<(), Box> { let metadata = figment.find_metadata(Self::PATH.unwrap()); let error_on_field = |mut error: figment::error::Error, field: &'static str| { @@ -201,29 +204,29 @@ impl ConfigurationSection for EmailConfig { EmailTransportKind::Smtp => { if let Err(e) = Mailbox::from_str(&self.from) { - return Err(error_on_field(figment::error::Error::custom(e), "from")); + return Err(error_on_field(figment::error::Error::custom(e), "from").into()); } if let Err(e) = Mailbox::from_str(&self.reply_to) { - return Err(error_on_field(figment::error::Error::custom(e), "reply_to")); + return Err(error_on_field(figment::error::Error::custom(e), "reply_to").into()); } match (self.username.is_some(), self.password.is_some()) { (true, true) | (false, false) => {} (true, false) => { - return Err(missing_field("password")); + return Err(missing_field("password").into()); } (false, true) => { - return Err(missing_field("username")); + return Err(missing_field("username").into()); } } if self.mode.is_none() { - return Err(missing_field("mode")); + return Err(missing_field("mode").into()); } if self.hostname.is_none() { - return Err(missing_field("hostname")); + return Err(missing_field("hostname").into()); } if self.command.is_some() { @@ -239,7 +242,8 @@ impl ConfigurationSection for EmailConfig { "username", "password", ], - )); + ) + .into()); } } @@ -247,35 +251,35 @@ impl ConfigurationSection for EmailConfig { let expected_fields = &["from", "reply_to", "transport", "command"]; if let Err(e) = Mailbox::from_str(&self.from) { - return Err(error_on_field(figment::error::Error::custom(e), "from")); + return Err(error_on_field(figment::error::Error::custom(e), "from").into()); } if let Err(e) = Mailbox::from_str(&self.reply_to) { - return Err(error_on_field(figment::error::Error::custom(e), "reply_to")); + return Err(error_on_field(figment::error::Error::custom(e), "reply_to").into()); } if self.command.is_none() { - return Err(missing_field("command")); + return Err(missing_field("command").into()); } if self.mode.is_some() { - return Err(unexpected_field("mode", expected_fields)); + return Err(unexpected_field("mode", expected_fields).into()); } if self.hostname.is_some() { - return Err(unexpected_field("hostname", expected_fields)); + return Err(unexpected_field("hostname", expected_fields).into()); } if self.port.is_some() { - return Err(unexpected_field("port", expected_fields)); + return Err(unexpected_field("port", expected_fields).into()); } if self.username.is_some() { - return Err(unexpected_field("username", expected_fields)); + return Err(unexpected_field("username", expected_fields).into()); } if self.password.is_some() { - return Err(unexpected_field("password", expected_fields)); + return Err(unexpected_field("password", expected_fields).into()); } } } diff --git a/crates/config/src/sections/http.rs b/crates/config/src/sections/http.rs index 51bce2d38..c75d47fd3 100644 --- a/crates/config/src/sections/http.rs +++ b/crates/config/src/sections/http.rs @@ -412,7 +412,10 @@ impl Default for HttpConfig { impl ConfigurationSection for HttpConfig { const PATH: Option<&'static str> = Some("http"); - fn validate(&self, figment: &figment::Figment) -> Result<(), figment::Error> { + fn validate( + &self, + figment: &figment::Figment, + ) -> Result<(), Box> { for (index, listener) in self.listeners.iter().enumerate() { let annotate = |mut error: figment::Error| { error.metadata = figment @@ -424,49 +427,57 @@ impl ConfigurationSection for HttpConfig { "listeners".to_owned(), index.to_string(), ]; - Err(error) + error }; if listener.resources.is_empty() { - return annotate(figment::Error::from("listener has no resources".to_owned())); + return Err( + annotate(figment::Error::from("listener has no resources".to_owned())).into(), + ); } if listener.binds.is_empty() { - return annotate(figment::Error::from( + return Err(annotate(figment::Error::from( "listener does not bind to any address".to_owned(), - )); + )) + .into()); } if let Some(tls_config) = &listener.tls { if tls_config.certificate.is_some() && tls_config.certificate_file.is_some() { - return annotate(figment::Error::from( + return Err(annotate(figment::Error::from( "Only one of `certificate` or `certificate_file` can be set at a time" .to_owned(), - )); + )) + .into()); } if tls_config.certificate.is_none() && tls_config.certificate_file.is_none() { - return annotate(figment::Error::from( + return Err(annotate(figment::Error::from( "TLS configuration is missing a certificate".to_owned(), - )); + )) + .into()); } if tls_config.key.is_some() && tls_config.key_file.is_some() { - return annotate(figment::Error::from( + return Err(annotate(figment::Error::from( "Only one of `key` or `key_file` can be set at a time".to_owned(), - )); + )) + .into()); } if tls_config.key.is_none() && tls_config.key_file.is_none() { - return annotate(figment::Error::from( + return Err(annotate(figment::Error::from( "TLS configuration is missing a private key".to_owned(), - )); + )) + .into()); } if tls_config.password.is_some() && tls_config.password_file.is_some() { - return annotate(figment::Error::from( + return Err(annotate(figment::Error::from( "Only one of `password` or `password_file` can be set at a time".to_owned(), - )); + )) + .into()); } } } diff --git a/crates/config/src/sections/mod.rs b/crates/config/src/sections/mod.rs index 850ca0c61..ed38fa9b6 100644 --- a/crates/config/src/sections/mod.rs +++ b/crates/config/src/sections/mod.rs @@ -130,7 +130,10 @@ pub struct RootConfig { } impl ConfigurationSection for RootConfig { - fn validate(&self, figment: &figment::Figment) -> Result<(), figment::Error> { + fn validate( + &self, + figment: &figment::Figment, + ) -> Result<(), Box> { self.clients.validate(figment)?; self.http.validate(figment)?; self.database.validate(figment)?; @@ -249,7 +252,10 @@ pub struct AppConfig { } impl ConfigurationSection for AppConfig { - fn validate(&self, figment: &figment::Figment) -> Result<(), figment::Error> { + fn validate( + &self, + figment: &figment::Figment, + ) -> Result<(), Box> { self.http.validate(figment)?; self.database.validate(figment)?; self.templates.validate(figment)?; @@ -285,7 +291,10 @@ pub struct SyncConfig { } impl ConfigurationSection for SyncConfig { - fn validate(&self, figment: &figment::Figment) -> Result<(), figment::Error> { + fn validate( + &self, + figment: &figment::Figment, + ) -> Result<(), Box> { self.database.validate(figment)?; self.secrets.validate(figment)?; self.clients.validate(figment)?; diff --git a/crates/config/src/sections/passwords.rs b/crates/config/src/sections/passwords.rs index 2870894b6..b15c009fc 100644 --- a/crates/config/src/sections/passwords.rs +++ b/crates/config/src/sections/passwords.rs @@ -72,12 +72,15 @@ impl Default for PasswordsConfig { impl ConfigurationSection for PasswordsConfig { const PATH: Option<&'static str> = Some("passwords"); - fn validate(&self, figment: &figment::Figment) -> Result<(), figment::Error> { + fn validate( + &self, + figment: &figment::Figment, + ) -> Result<(), Box> { let annotate = |mut error: figment::Error| { error.metadata = figment.find_metadata(Self::PATH.unwrap()).cloned(); error.profile = Some(figment::Profile::Default); error.path = vec![Self::PATH.unwrap().to_owned()]; - Err(error) + error }; if !self.enabled { @@ -86,16 +89,18 @@ impl ConfigurationSection for PasswordsConfig { } if self.schemes.is_empty() { - return annotate(figment::Error::from( + return Err(annotate(figment::Error::from( "Requires at least one password scheme in the config".to_owned(), - )); + )) + .into()); } for scheme in &self.schemes { if scheme.secret.is_some() && scheme.secret_file.is_some() { - return annotate(figment::Error::from( + return Err(annotate(figment::Error::from( "Cannot specify both `secret` and `secret_file`".to_owned(), - )); + )) + .into()); } } diff --git a/crates/config/src/sections/rate_limiting.rs b/crates/config/src/sections/rate_limiting.rs index b3ef6fdbd..0b7c95dbc 100644 --- a/crates/config/src/sections/rate_limiting.rs +++ b/crates/config/src/sections/rate_limiting.rs @@ -117,7 +117,10 @@ pub struct RateLimiterConfiguration { impl ConfigurationSection for RateLimitingConfig { const PATH: Option<&'static str> = Some("rate_limiting"); - fn validate(&self, figment: &figment::Figment) -> Result<(), figment::Error> { + fn validate( + &self, + figment: &figment::Figment, + ) -> Result<(), Box> { let metadata = figment.find_metadata(Self::PATH.unwrap()); let error_on_field = |mut error: figment::error::Error, field: &'static str| { @@ -154,25 +157,21 @@ impl ConfigurationSection for RateLimitingConfig { }; if let Some(error) = error_on_limiter(&self.account_recovery.per_ip) { - return Err(error_on_nested_field(error, "account_recovery", "per_ip")); + return Err(error_on_nested_field(error, "account_recovery", "per_ip").into()); } if let Some(error) = error_on_limiter(&self.account_recovery.per_address) { - return Err(error_on_nested_field( - error, - "account_recovery", - "per_address", - )); + return Err(error_on_nested_field(error, "account_recovery", "per_address").into()); } if let Some(error) = error_on_limiter(&self.registration) { - return Err(error_on_field(error, "registration")); + return Err(error_on_field(error, "registration").into()); } if let Some(error) = error_on_limiter(&self.login.per_ip) { - return Err(error_on_nested_field(error, "login", "per_ip")); + return Err(error_on_nested_field(error, "login", "per_ip").into()); } if let Some(error) = error_on_limiter(&self.login.per_account) { - return Err(error_on_nested_field(error, "login", "per_account")); + return Err(error_on_nested_field(error, "login", "per_account").into()); } Ok(()) diff --git a/crates/config/src/sections/telemetry.rs b/crates/config/src/sections/telemetry.rs index f42e29f52..8e2d995e9 100644 --- a/crates/config/src/sections/telemetry.rs +++ b/crates/config/src/sections/telemetry.rs @@ -194,13 +194,17 @@ impl TelemetryConfig { impl ConfigurationSection for TelemetryConfig { const PATH: Option<&'static str> = Some("telemetry"); - fn validate(&self, _figment: &figment::Figment) -> Result<(), figment::Error> { + fn validate( + &self, + _figment: &figment::Figment, + ) -> Result<(), Box> { if let Some(sample_rate) = self.sentry.sample_rate { if !(0.0..=1.0).contains(&sample_rate) { return Err(figment::error::Error::custom( "Sentry sample rate must be between 0.0 and 1.0", ) - .with_path("sentry.sample_rate")); + .with_path("sentry.sample_rate") + .into()); } } @@ -209,7 +213,8 @@ impl ConfigurationSection for TelemetryConfig { return Err(figment::error::Error::custom( "Sentry sample rate must be between 0.0 and 1.0", ) - .with_path("sentry.traces_sample_rate")); + .with_path("sentry.traces_sample_rate") + .into()); } } @@ -218,7 +223,8 @@ impl ConfigurationSection for TelemetryConfig { return Err(figment::error::Error::custom( "Tracing sample rate must be between 0.0 and 1.0", ) - .with_path("tracing.sample_rate")); + .with_path("tracing.sample_rate") + .into()); } } diff --git a/crates/config/src/sections/upstream_oauth2.rs b/crates/config/src/sections/upstream_oauth2.rs index 2162c9fe4..8d6229848 100644 --- a/crates/config/src/sections/upstream_oauth2.rs +++ b/crates/config/src/sections/upstream_oauth2.rs @@ -33,7 +33,10 @@ impl UpstreamOAuth2Config { impl ConfigurationSection for UpstreamOAuth2Config { const PATH: Option<&'static str> = Some("upstream_oauth2"); - fn validate(&self, figment: &figment::Figment) -> Result<(), figment::Error> { + fn validate( + &self, + figment: &figment::Figment, + ) -> Result<(), Box> { for (index, provider) in self.providers.iter().enumerate() { let annotate = |mut error: figment::Error| { error.metadata = figment @@ -45,15 +48,16 @@ impl ConfigurationSection for UpstreamOAuth2Config { "providers".to_owned(), index.to_string(), ]; - Err(error) + error }; if !matches!(provider.discovery_mode, DiscoveryMode::Disabled) && provider.issuer.is_none() { - return annotate(figment::Error::custom( + return Err(annotate(figment::Error::custom( "The `issuer` field is required when discovery is enabled", - )); + )) + .into()); } match provider.token_endpoint_auth_method { @@ -61,16 +65,16 @@ impl ConfigurationSection for UpstreamOAuth2Config { | TokenAuthMethod::PrivateKeyJwt | TokenAuthMethod::SignInWithApple => { if provider.client_secret.is_some() { - return annotate(figment::Error::custom( + return Err(annotate(figment::Error::custom( "Unexpected field `client_secret` for the selected authentication method", - )); + )).into()); } } TokenAuthMethod::ClientSecretBasic | TokenAuthMethod::ClientSecretPost | TokenAuthMethod::ClientSecretJwt => { if provider.client_secret.is_none() { - return annotate(figment::Error::missing_field("client_secret")); + return Err(annotate(figment::Error::missing_field("client_secret")).into()); } } } @@ -81,16 +85,17 @@ impl ConfigurationSection for UpstreamOAuth2Config { | TokenAuthMethod::ClientSecretPost | TokenAuthMethod::SignInWithApple => { if provider.token_endpoint_auth_signing_alg.is_some() { - return annotate(figment::Error::custom( + return Err(annotate(figment::Error::custom( "Unexpected field `token_endpoint_auth_signing_alg` for the selected authentication method", - )); + )).into()); } } TokenAuthMethod::ClientSecretJwt | TokenAuthMethod::PrivateKeyJwt => { if provider.token_endpoint_auth_signing_alg.is_none() { - return annotate(figment::Error::missing_field( + return Err(annotate(figment::Error::missing_field( "token_endpoint_auth_signing_alg", - )); + )) + .into()); } } } @@ -98,15 +103,17 @@ impl ConfigurationSection for UpstreamOAuth2Config { match provider.token_endpoint_auth_method { TokenAuthMethod::SignInWithApple => { if provider.sign_in_with_apple.is_none() { - return annotate(figment::Error::missing_field("sign_in_with_apple")); + return Err( + annotate(figment::Error::missing_field("sign_in_with_apple")).into(), + ); } } _ => { if provider.sign_in_with_apple.is_some() { - return annotate(figment::Error::custom( + return Err(annotate(figment::Error::custom( "Unexpected field `sign_in_with_apple` for the selected authentication method", - )); + )).into()); } } } diff --git a/crates/config/src/util.rs b/crates/config/src/util.rs index d7aa4829b..d6cf58c3f 100644 --- a/crates/config/src/util.rs +++ b/crates/config/src/util.rs @@ -4,7 +4,7 @@ // SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial // Please see LICENSE files in the repository root for full details. -use figment::{Figment, error::Error as FigmentError}; +use figment::Figment; use serde::de::DeserializeOwned; /// Trait implemented by all configuration section to help loading specific part @@ -18,7 +18,10 @@ pub trait ConfigurationSection: Sized + DeserializeOwned { /// # Errors /// /// Returns an error if the configuration is invalid - fn validate(&self, _figment: &Figment) -> Result<(), FigmentError> { + fn validate( + &self, + _figment: &Figment, + ) -> Result<(), Box> { Ok(()) } @@ -27,7 +30,9 @@ pub trait ConfigurationSection: Sized + DeserializeOwned { /// # Errors /// /// Returns an error if the configuration could not be loaded - fn extract(figment: &Figment) -> Result { + fn extract( + figment: &Figment, + ) -> Result> { let this: Self = if let Some(path) = Self::PATH { figment.extract_inner(path)? } else { @@ -49,7 +54,9 @@ pub trait ConfigurationSectionExt: ConfigurationSection + Default { /// # Errors /// /// Returns an error if the configuration section is invalid. - fn extract_or_default(figment: &Figment) -> Result { + fn extract_or_default( + figment: &Figment, + ) -> Result> { let this: Self = if let Some(path) = Self::PATH { // If the configuration section is not present, we return the default value if !figment.contains(path) { diff --git a/crates/handlers/src/graphql/mutations/oauth2_session.rs b/crates/handlers/src/graphql/mutations/oauth2_session.rs index 560fb17e1..0de5b16a2 100644 --- a/crates/handlers/src/graphql/mutations/oauth2_session.rs +++ b/crates/handlers/src/graphql/mutations/oauth2_session.rs @@ -78,7 +78,7 @@ pub struct EndOAuth2SessionInput { /// The payload of the `endOauth2Session` mutation. pub enum EndOAuth2SessionPayload { NotFound, - Ended(mas_data_model::Session), + Ended(Box), } /// The status of the `endOauth2Session` mutation. @@ -104,7 +104,7 @@ impl EndOAuth2SessionPayload { /// Returns the ended session. async fn oauth2_session(&self) -> Option { match self { - Self::Ended(session) => Some(OAuth2Session(session.clone())), + Self::Ended(session) => Some(OAuth2Session(*session.clone())), Self::NotFound => None, } } @@ -126,7 +126,7 @@ pub enum SetOAuth2SessionNamePayload { NotFound, /// The session was updated. - Updated(mas_data_model::Session), + Updated(Box), } /// The status of the `setOauth2SessionName` mutation. @@ -152,7 +152,7 @@ impl SetOAuth2SessionNamePayload { /// The session that was updated. async fn oauth2_session(&self) -> Option { match self { - Self::Updated(session) => Some(OAuth2Session(session.clone())), + Self::Updated(session) => Some(OAuth2Session(*session.clone())), Self::NotFound => None, } } @@ -293,7 +293,7 @@ impl OAuth2SessionMutations { repo.save().await?; - Ok(EndOAuth2SessionPayload::Ended(session)) + Ok(EndOAuth2SessionPayload::Ended(Box::new(session))) } async fn set_oauth2_session_name( @@ -343,6 +343,6 @@ impl OAuth2SessionMutations { repo.save().await?; - Ok(SetOAuth2SessionNamePayload::Updated(session)) + Ok(SetOAuth2SessionNamePayload::Updated(Box::new(session))) } } diff --git a/crates/jose/src/claims.rs b/crates/jose/src/claims.rs index a4449695f..3ab6e26e0 100644 --- a/crates/jose/src/claims.rs +++ b/crates/jose/src/claims.rs @@ -49,7 +49,7 @@ impl Validator for () { } pub struct Claim { - claim: &'static str, + value: &'static str, t: PhantomData, v: PhantomData, } @@ -61,7 +61,7 @@ where #[must_use] pub const fn new(claim: &'static str) -> Self { Self { - claim, + value: claim, t: PhantomData, v: PhantomData, } @@ -83,8 +83,8 @@ where { let value = value.into(); let value: serde_json::Value = - serde_json::to_value(&value).map_err(|_| ClaimError::InvalidClaim(self.claim))?; - claims.insert(self.claim.to_owned(), value); + serde_json::to_value(&value).map_err(|_| ClaimError::InvalidClaim(self.value))?; + claims.insert(self.value.to_owned(), value); Ok(()) } @@ -126,15 +126,15 @@ where { let validator: V = validator.into(); let claim = claims - .remove(self.claim) - .ok_or(ClaimError::MissingClaim(self.claim))?; + .remove(self.value) + .ok_or(ClaimError::MissingClaim(self.value))?; let res = - serde_json::from_value(claim).map_err(|_| ClaimError::InvalidClaim(self.claim))?; + serde_json::from_value(claim).map_err(|_| ClaimError::InvalidClaim(self.value))?; validator .validate(&res) .map_err(|source| ClaimError::ValidationError { - claim: self.claim, + claim: self.value, source: Box::new(source), })?; Ok(res) @@ -192,8 +192,8 @@ where &self, claims: &HashMap, ) -> Result<(), ClaimError> { - if claims.contains_key(self.claim) { - Err(ClaimError::InvalidClaim(self.claim)) + if claims.contains_key(self.value) { + Err(ClaimError::InvalidClaim(self.value)) } else { Ok(()) } diff --git a/crates/policy/src/bin/schema.rs b/crates/policy/src/bin/schema.rs index 11cbb1fdc..cc908d4c9 100644 --- a/crates/policy/src/bin/schema.rs +++ b/crates/policy/src/bin/schema.rs @@ -19,7 +19,7 @@ use schemars::{JsonSchema, r#gen::SchemaSettings}; fn write_schema(out_dir: Option<&Path>, file: &str) { let mut writer: Box = if let Some(out_dir) = out_dir { let path = out_dir.join(file); - eprintln!("Writing to {path:?}"); + eprintln!("Writing to {}", path.display()); let file = std::fs::File::create(path).expect("Failed to create file"); Box::new(std::io::BufWriter::new(file)) } else { diff --git a/crates/syn2mas/src/synapse_reader/checks.rs b/crates/syn2mas/src/synapse_reader/checks.rs index 6ae5ce530..655642770 100644 --- a/crates/syn2mas/src/synapse_reader/checks.rs +++ b/crates/syn2mas/src/synapse_reader/checks.rs @@ -25,7 +25,7 @@ pub enum Error { Sqlx(#[from] sqlx::Error), #[error("failed to load MAS config: {0}")] - MasConfig(#[from] figment::Error), + MasConfig(#[source] Box), #[error("failed to load MAS password config: {0}")] MasPasswordConfig(#[source] anyhow::Error), @@ -188,13 +188,13 @@ pub async fn synapse_config_check_against_mas_config( let mut errors = Vec::new(); let mut warnings = Vec::new(); - let mas_passwords = PasswordsConfig::extract_or_default(mas)?; + let mas_passwords = PasswordsConfig::extract_or_default(mas).map_err(Error::MasConfig)?; let mas_password_schemes = mas_passwords .load() .await .map_err(Error::MasPasswordConfig)?; - let mas_matrix = MatrixConfig::extract(mas)?; + let mas_matrix = MatrixConfig::extract(mas).map_err(Error::MasConfig)?; // Look for the MAS password hashing scheme that will be used for imported // Synapse passwords, then check the configuration matches so that Synapse @@ -230,12 +230,12 @@ pub async fn synapse_config_check_against_mas_config( }); } - let mas_captcha = CaptchaConfig::extract_or_default(mas)?; + let mas_captcha = CaptchaConfig::extract_or_default(mas).map_err(Error::MasConfig)?; if synapse.enable_registration_captcha && mas_captcha.service.is_none() { warnings.push(CheckWarning::ShouldPortRegistrationCaptcha); } - let mas_branding = BrandingConfig::extract_or_default(mas)?; + let mas_branding = BrandingConfig::extract_or_default(mas).map_err(Error::MasConfig)?; if synapse.user_consent.is_some() && mas_branding.tos_uri.is_none() { warnings.push(CheckWarning::ShouldPortUserConsentAsTerms); } @@ -295,7 +295,7 @@ pub async fn synapse_database_check( .await?; if !oauth_provider_user_counts.is_empty() { let syn_oauth2 = synapse.all_oidc_providers(); - let mas_oauth2 = UpstreamOAuth2Config::extract_or_default(mas)?; + let mas_oauth2 = UpstreamOAuth2Config::extract_or_default(mas).map_err(Error::MasConfig)?; for row in oauth_provider_user_counts { // This is a special case of a previous migration attempt to MAS if row.auth_provider == "oauth-delegated" { diff --git a/crates/syn2mas/src/synapse_reader/config/mod.rs b/crates/syn2mas/src/synapse_reader/config/mod.rs index d837bff2c..4bd89921b 100644 --- a/crates/syn2mas/src/synapse_reader/config/mod.rs +++ b/crates/syn2mas/src/synapse_reader/config/mod.rs @@ -94,7 +94,9 @@ impl Config { /// /// - If there is a problem reading any of the files. /// - If the configuration is not valid. - pub fn load(files: &[Utf8PathBuf]) -> Result { + pub fn load( + files: &[Utf8PathBuf], + ) -> Result> { let mut figment = figment::Figment::new(); for file in files { // TODO this is not exactly correct behaviour — Synapse does not merge anything @@ -103,7 +105,8 @@ impl Config { // https://github.com/element-hq/synapse/blob/develop/synapse/config/_base.py?rgh-link-date=2025-01-20T17%3A02%3A56Z#L870 figment = figment.merge(Yaml::file(file)); } - figment.extract::() + let config = figment.extract::()?; + Ok(config) } /// Returns a map of all OIDC providers from the Synapse configuration.