1use anyhow::bail;
8use camino::Utf8PathBuf;
9use rand::Rng;
10use schemars::JsonSchema;
11use serde::{Deserialize, Serialize};
12
13mod account;
14mod branding;
15mod captcha;
16mod clients;
17mod database;
18mod email;
19mod experimental;
20mod http;
21mod matrix;
22mod passwords;
23mod policy;
24mod rate_limiting;
25mod secrets;
26mod telemetry;
27mod templates;
28mod upstream_oauth2;
29
30pub use self::{
31 account::AccountConfig,
32 branding::BrandingConfig,
33 captcha::{CaptchaConfig, CaptchaServiceKind},
34 clients::{ClientAuthMethodConfig, ClientConfig, ClientsConfig},
35 database::{DatabaseConfig, PgSslMode},
36 email::{EmailConfig, EmailSmtpMode, EmailTransportKind},
37 experimental::ExperimentalConfig,
38 http::{
39 BindConfig as HttpBindConfig, HttpConfig, ListenerConfig as HttpListenerConfig,
40 Resource as HttpResource, TlsConfig as HttpTlsConfig, UnixOrTcp,
41 },
42 matrix::{HomeserverKind, MatrixConfig},
43 passwords::{
44 Algorithm as PasswordAlgorithm, HashingScheme as PasswordHashingScheme, PasswordsConfig,
45 },
46 policy::PolicyConfig,
47 rate_limiting::RateLimitingConfig,
48 secrets::SecretsConfig,
49 telemetry::{
50 MetricsConfig, MetricsExporterKind, Propagator, TelemetryConfig, TracingConfig,
51 TracingExporterKind,
52 },
53 templates::TemplatesConfig,
54 upstream_oauth2::{
55 ClaimsImports as UpstreamOAuth2ClaimsImports, DiscoveryMode as UpstreamOAuth2DiscoveryMode,
56 EmailImportPreference as UpstreamOAuth2EmailImportPreference,
57 ImportAction as UpstreamOAuth2ImportAction,
58 OnBackchannelLogout as UpstreamOAuth2OnBackchannelLogout,
59 OnConflict as UpstreamOAuth2OnConflict, PkceMethod as UpstreamOAuth2PkceMethod,
60 Provider as UpstreamOAuth2Provider, ResponseMode as UpstreamOAuth2ResponseMode,
61 TokenAuthMethod as UpstreamOAuth2TokenAuthMethod, UpstreamOAuth2Config,
62 },
63};
64use crate::util::ConfigurationSection;
65
66#[derive(Debug, Serialize, Deserialize, JsonSchema)]
68pub struct RootConfig {
69 #[serde(default, skip_serializing_if = "ClientsConfig::is_default")]
71 pub clients: ClientsConfig,
72
73 #[serde(default)]
75 pub http: HttpConfig,
76
77 #[serde(default)]
79 pub database: DatabaseConfig,
80
81 #[serde(default, skip_serializing_if = "TelemetryConfig::is_default")]
83 pub telemetry: TelemetryConfig,
84
85 #[serde(default, skip_serializing_if = "TemplatesConfig::is_default")]
87 pub templates: TemplatesConfig,
88
89 #[serde(default)]
91 pub email: EmailConfig,
92
93 pub secrets: SecretsConfig,
95
96 #[serde(default)]
98 pub passwords: PasswordsConfig,
99
100 pub matrix: MatrixConfig,
102
103 #[serde(default, skip_serializing_if = "PolicyConfig::is_default")]
105 pub policy: PolicyConfig,
106
107 #[serde(default, skip_serializing_if = "RateLimitingConfig::is_default")]
110 pub rate_limiting: RateLimitingConfig,
111
112 #[serde(default, skip_serializing_if = "UpstreamOAuth2Config::is_default")]
114 pub upstream_oauth2: UpstreamOAuth2Config,
115
116 #[serde(default, skip_serializing_if = "BrandingConfig::is_default")]
118 pub branding: BrandingConfig,
119
120 #[serde(default, skip_serializing_if = "CaptchaConfig::is_default")]
122 pub captcha: CaptchaConfig,
123
124 #[serde(default, skip_serializing_if = "AccountConfig::is_default")]
127 pub account: AccountConfig,
128
129 #[serde(default, skip_serializing_if = "ExperimentalConfig::is_default")]
131 pub experimental: ExperimentalConfig,
132}
133
134impl ConfigurationSection for RootConfig {
135 fn validate(
136 &self,
137 figment: &figment::Figment,
138 ) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
139 self.clients.validate(figment)?;
140 self.http.validate(figment)?;
141 self.database.validate(figment)?;
142 self.telemetry.validate(figment)?;
143 self.templates.validate(figment)?;
144 self.email.validate(figment)?;
145 self.passwords.validate(figment)?;
146 self.secrets.validate(figment)?;
147 self.matrix.validate(figment)?;
148 self.policy.validate(figment)?;
149 self.rate_limiting.validate(figment)?;
150 self.upstream_oauth2.validate(figment)?;
151 self.branding.validate(figment)?;
152 self.captcha.validate(figment)?;
153 self.account.validate(figment)?;
154 self.experimental.validate(figment)?;
155
156 Ok(())
157 }
158}
159
160impl RootConfig {
161 pub async fn generate<R>(mut rng: R) -> anyhow::Result<Self>
167 where
168 R: Rng + Send,
169 {
170 Ok(Self {
171 clients: ClientsConfig::default(),
172 http: HttpConfig::default(),
173 database: DatabaseConfig::default(),
174 telemetry: TelemetryConfig::default(),
175 templates: TemplatesConfig::default(),
176 email: EmailConfig::default(),
177 passwords: PasswordsConfig::default(),
178 secrets: SecretsConfig::generate(&mut rng).await?,
179 matrix: MatrixConfig::generate(&mut rng),
180 policy: PolicyConfig::default(),
181 rate_limiting: RateLimitingConfig::default(),
182 upstream_oauth2: UpstreamOAuth2Config::default(),
183 branding: BrandingConfig::default(),
184 captcha: CaptchaConfig::default(),
185 account: AccountConfig::default(),
186 experimental: ExperimentalConfig::default(),
187 })
188 }
189
190 #[must_use]
192 pub fn test() -> Self {
193 Self {
194 clients: ClientsConfig::default(),
195 http: HttpConfig::default(),
196 database: DatabaseConfig::default(),
197 telemetry: TelemetryConfig::default(),
198 templates: TemplatesConfig::default(),
199 passwords: PasswordsConfig::default(),
200 email: EmailConfig::default(),
201 secrets: SecretsConfig::test(),
202 matrix: MatrixConfig::test(),
203 policy: PolicyConfig::default(),
204 rate_limiting: RateLimitingConfig::default(),
205 upstream_oauth2: UpstreamOAuth2Config::default(),
206 branding: BrandingConfig::default(),
207 captcha: CaptchaConfig::default(),
208 account: AccountConfig::default(),
209 experimental: ExperimentalConfig::default(),
210 }
211 }
212}
213
214#[allow(missing_docs)]
216#[derive(Debug, Deserialize)]
217pub struct AppConfig {
218 #[serde(default)]
219 pub http: HttpConfig,
220
221 #[serde(default)]
222 pub database: DatabaseConfig,
223
224 #[serde(default)]
225 pub templates: TemplatesConfig,
226
227 #[serde(default)]
228 pub email: EmailConfig,
229
230 pub secrets: SecretsConfig,
231
232 #[serde(default)]
233 pub passwords: PasswordsConfig,
234
235 pub matrix: MatrixConfig,
236
237 #[serde(default)]
238 pub policy: PolicyConfig,
239
240 #[serde(default)]
241 pub rate_limiting: RateLimitingConfig,
242
243 #[serde(default)]
244 pub branding: BrandingConfig,
245
246 #[serde(default)]
247 pub captcha: CaptchaConfig,
248
249 #[serde(default)]
250 pub account: AccountConfig,
251
252 #[serde(default)]
253 pub experimental: ExperimentalConfig,
254}
255
256impl ConfigurationSection for AppConfig {
257 fn validate(
258 &self,
259 figment: &figment::Figment,
260 ) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
261 self.http.validate(figment)?;
262 self.database.validate(figment)?;
263 self.templates.validate(figment)?;
264 self.email.validate(figment)?;
265 self.passwords.validate(figment)?;
266 self.secrets.validate(figment)?;
267 self.matrix.validate(figment)?;
268 self.policy.validate(figment)?;
269 self.rate_limiting.validate(figment)?;
270 self.branding.validate(figment)?;
271 self.captcha.validate(figment)?;
272 self.account.validate(figment)?;
273 self.experimental.validate(figment)?;
274
275 Ok(())
276 }
277}
278
279#[allow(missing_docs)]
281#[derive(Debug, Deserialize)]
282pub struct SyncConfig {
283 #[serde(default)]
284 pub database: DatabaseConfig,
285
286 pub secrets: SecretsConfig,
287
288 #[serde(default)]
289 pub clients: ClientsConfig,
290
291 #[serde(default)]
292 pub upstream_oauth2: UpstreamOAuth2Config,
293}
294
295impl ConfigurationSection for SyncConfig {
296 fn validate(
297 &self,
298 figment: &figment::Figment,
299 ) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
300 self.database.validate(figment)?;
301 self.secrets.validate(figment)?;
302 self.clients.validate(figment)?;
303 self.upstream_oauth2.validate(figment)?;
304
305 Ok(())
306 }
307}
308
309#[derive(Clone, Debug)]
314pub enum ClientSecret {
315 File(Utf8PathBuf),
317
318 Value(String),
320}
321
322#[derive(JsonSchema, Serialize, Deserialize, Clone, Debug)]
324pub struct ClientSecretRaw {
325 #[schemars(with = "Option<String>")]
329 #[serde(skip_serializing_if = "Option::is_none")]
330 client_secret_file: Option<Utf8PathBuf>,
331
332 #[serde(skip_serializing_if = "Option::is_none")]
335 client_secret: Option<String>,
336}
337
338impl ClientSecret {
339 pub async fn value(&self) -> anyhow::Result<String> {
347 Ok(match self {
348 ClientSecret::File(path) => tokio::fs::read_to_string(path).await?,
349 ClientSecret::Value(client_secret) => client_secret.clone(),
350 })
351 }
352}
353
354impl TryFrom<ClientSecretRaw> for Option<ClientSecret> {
355 type Error = anyhow::Error;
356
357 fn try_from(value: ClientSecretRaw) -> Result<Self, Self::Error> {
358 match (value.client_secret, value.client_secret_file) {
359 (None, None) => Ok(None),
360 (None, Some(path)) => Ok(Some(ClientSecret::File(path))),
361 (Some(client_secret), None) => Ok(Some(ClientSecret::Value(client_secret))),
362 (Some(_), Some(_)) => {
363 bail!("Cannot specify both `client_secret` and `client_secret_file`")
364 }
365 }
366 }
367}
368
369impl From<Option<ClientSecret>> for ClientSecretRaw {
370 fn from(value: Option<ClientSecret>) -> Self {
371 match value {
372 Some(ClientSecret::File(path)) => ClientSecretRaw {
373 client_secret_file: Some(path),
374 client_secret: None,
375 },
376 Some(ClientSecret::Value(client_secret)) => ClientSecretRaw {
377 client_secret_file: None,
378 client_secret: Some(client_secret),
379 },
380 None => ClientSecretRaw {
381 client_secret_file: None,
382 client_secret: None,
383 },
384 }
385 }
386}