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 oauth;
23mod passwords;
24mod policy;
25mod rate_limiting;
26mod secrets;
27mod telemetry;
28mod templates;
29mod upstream_oauth2;
30
31pub use self::{
32 account::AccountConfig,
33 branding::BrandingConfig,
34 captcha::{CaptchaConfig, CaptchaServiceKind},
35 clients::{ClientAuthMethodConfig, ClientConfig, ClientsConfig},
36 database::{DatabaseConfig, PgSslMode},
37 email::{EmailConfig, EmailSmtpMode, EmailTransportKind},
38 experimental::{ExperimentalConfig, SessionLimitConfig as ExperimentalSessionLimitConfig},
39 http::{
40 BindConfig as HttpBindConfig, HttpConfig, ListenerConfig as HttpListenerConfig,
41 Resource as HttpResource, TlsConfig as HttpTlsConfig, UnixOrTcp,
42 },
43 matrix::{HomeserverKind, MatrixConfig},
44 oauth::OAuthConfig,
45 passwords::{
46 Algorithm as PasswordAlgorithm, HashingScheme as PasswordHashingScheme, PasswordsConfig,
47 },
48 policy::PolicyConfig,
49 rate_limiting::RateLimitingConfig,
50 secrets::SecretsConfig,
51 telemetry::{
52 MetricsConfig, MetricsExporterKind, Propagator, TelemetryConfig, TracingConfig,
53 TracingExporterKind,
54 },
55 templates::TemplatesConfig,
56 upstream_oauth2::{
57 ClaimsImports as UpstreamOAuth2ClaimsImports, DiscoveryMode as UpstreamOAuth2DiscoveryMode,
58 EmailImportPreference as UpstreamOAuth2EmailImportPreference,
59 ImportAction as UpstreamOAuth2ImportAction,
60 OnBackchannelLogout as UpstreamOAuth2OnBackchannelLogout,
61 OnConflict as UpstreamOAuth2OnConflict, PkceMethod as UpstreamOAuth2PkceMethod,
62 Provider as UpstreamOAuth2Provider, ResponseMode as UpstreamOAuth2ResponseMode,
63 TokenAuthMethod as UpstreamOAuth2TokenAuthMethod, UpstreamOAuth2Config,
64 },
65};
66use crate::util::ConfigurationSection;
67
68#[derive(Debug, Serialize, Deserialize, JsonSchema)]
70pub struct RootConfig {
71 #[serde(default, skip_serializing_if = "ClientsConfig::is_default")]
73 pub clients: ClientsConfig,
74
75 #[serde(default)]
77 pub http: HttpConfig,
78
79 #[serde(default)]
81 pub database: DatabaseConfig,
82
83 #[serde(default, skip_serializing_if = "TelemetryConfig::is_default")]
85 pub telemetry: TelemetryConfig,
86
87 #[serde(default, skip_serializing_if = "TemplatesConfig::is_default")]
89 pub templates: TemplatesConfig,
90
91 #[serde(default)]
93 pub email: EmailConfig,
94
95 pub secrets: SecretsConfig,
97
98 #[serde(default)]
100 pub passwords: PasswordsConfig,
101
102 pub matrix: MatrixConfig,
104
105 #[serde(default, skip_serializing_if = "PolicyConfig::is_default")]
107 pub policy: PolicyConfig,
108
109 #[serde(default, skip_serializing_if = "RateLimitingConfig::is_default")]
112 pub rate_limiting: RateLimitingConfig,
113
114 #[serde(default, skip_serializing_if = "UpstreamOAuth2Config::is_default")]
116 pub upstream_oauth2: UpstreamOAuth2Config,
117
118 #[serde(default, skip_serializing_if = "BrandingConfig::is_default")]
120 pub branding: BrandingConfig,
121
122 #[serde(default, skip_serializing_if = "CaptchaConfig::is_default")]
124 pub captcha: CaptchaConfig,
125
126 #[serde(default, skip_serializing_if = "AccountConfig::is_default")]
129 pub account: AccountConfig,
130
131 #[serde(default, skip_serializing_if = "OAuthConfig::is_default")]
133 pub oauth: OAuthConfig,
134
135 #[serde(default, skip_serializing_if = "ExperimentalConfig::is_default")]
137 pub experimental: ExperimentalConfig,
138}
139
140impl ConfigurationSection for RootConfig {
141 fn validate(
142 &self,
143 figment: &figment::Figment,
144 ) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
145 self.clients.validate(figment)?;
146 self.http.validate(figment)?;
147 self.database.validate(figment)?;
148 self.telemetry.validate(figment)?;
149 self.templates.validate(figment)?;
150 self.email.validate(figment)?;
151 self.passwords.validate(figment)?;
152 self.secrets.validate(figment)?;
153 self.matrix.validate(figment)?;
154 self.policy.validate(figment)?;
155 self.rate_limiting.validate(figment)?;
156 self.upstream_oauth2.validate(figment)?;
157 self.branding.validate(figment)?;
158 self.captcha.validate(figment)?;
159 self.account.validate(figment)?;
160 self.oauth.validate(figment)?;
161 self.experimental.validate(figment)?;
162
163 Ok(())
164 }
165}
166
167impl RootConfig {
168 pub async fn generate<R>(mut rng: R) -> anyhow::Result<Self>
174 where
175 R: Rng + Send,
176 {
177 Ok(Self {
178 clients: ClientsConfig::default(),
179 http: HttpConfig::default(),
180 database: DatabaseConfig::default(),
181 telemetry: TelemetryConfig::default(),
182 templates: TemplatesConfig::default(),
183 email: EmailConfig::default(),
184 passwords: PasswordsConfig::default(),
185 secrets: SecretsConfig::generate(&mut rng).await?,
186 matrix: MatrixConfig::generate(&mut rng),
187 policy: PolicyConfig::default(),
188 rate_limiting: RateLimitingConfig::default(),
189 upstream_oauth2: UpstreamOAuth2Config::default(),
190 branding: BrandingConfig::default(),
191 captcha: CaptchaConfig::default(),
192 account: AccountConfig::default(),
193 oauth: OAuthConfig::default(),
194 experimental: ExperimentalConfig::default(),
195 })
196 }
197
198 #[must_use]
200 pub fn test() -> Self {
201 Self {
202 clients: ClientsConfig::default(),
203 http: HttpConfig::default(),
204 database: DatabaseConfig::default(),
205 telemetry: TelemetryConfig::default(),
206 templates: TemplatesConfig::default(),
207 passwords: PasswordsConfig::default(),
208 email: EmailConfig::default(),
209 secrets: SecretsConfig::test(),
210 matrix: MatrixConfig::test(),
211 policy: PolicyConfig::default(),
212 rate_limiting: RateLimitingConfig::default(),
213 upstream_oauth2: UpstreamOAuth2Config::default(),
214 branding: BrandingConfig::default(),
215 captcha: CaptchaConfig::default(),
216 account: AccountConfig::default(),
217 oauth: OAuthConfig::default(),
218 experimental: ExperimentalConfig::default(),
219 }
220 }
221}
222
223#[allow(missing_docs)]
225#[derive(Debug, Deserialize)]
226pub struct AppConfig {
227 #[serde(default)]
228 pub http: HttpConfig,
229
230 #[serde(default)]
231 pub database: DatabaseConfig,
232
233 #[serde(default)]
234 pub templates: TemplatesConfig,
235
236 #[serde(default)]
237 pub email: EmailConfig,
238
239 pub secrets: SecretsConfig,
240
241 #[serde(default)]
242 pub passwords: PasswordsConfig,
243
244 pub matrix: MatrixConfig,
245
246 #[serde(default)]
247 pub policy: PolicyConfig,
248
249 #[serde(default)]
250 pub rate_limiting: RateLimitingConfig,
251
252 #[serde(default)]
253 pub branding: BrandingConfig,
254
255 #[serde(default)]
256 pub captcha: CaptchaConfig,
257
258 #[serde(default)]
259 pub account: AccountConfig,
260
261 #[serde(default)]
262 pub oauth: OAuthConfig,
263
264 #[serde(default)]
265 pub experimental: ExperimentalConfig,
266}
267
268impl ConfigurationSection for AppConfig {
269 fn validate(
270 &self,
271 figment: &figment::Figment,
272 ) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
273 self.http.validate(figment)?;
274 self.database.validate(figment)?;
275 self.templates.validate(figment)?;
276 self.email.validate(figment)?;
277 self.passwords.validate(figment)?;
278 self.secrets.validate(figment)?;
279 self.matrix.validate(figment)?;
280 self.policy.validate(figment)?;
281 self.rate_limiting.validate(figment)?;
282 self.branding.validate(figment)?;
283 self.captcha.validate(figment)?;
284 self.account.validate(figment)?;
285 self.oauth.validate(figment)?;
286 self.experimental.validate(figment)?;
287
288 Ok(())
289 }
290}
291
292#[allow(missing_docs)]
294#[derive(Debug, Deserialize)]
295pub struct SyncConfig {
296 #[serde(default)]
297 pub database: DatabaseConfig,
298
299 pub secrets: SecretsConfig,
300
301 #[serde(default)]
302 pub clients: ClientsConfig,
303
304 #[serde(default)]
305 pub upstream_oauth2: UpstreamOAuth2Config,
306}
307
308impl ConfigurationSection for SyncConfig {
309 fn validate(
310 &self,
311 figment: &figment::Figment,
312 ) -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
313 self.database.validate(figment)?;
314 self.secrets.validate(figment)?;
315 self.clients.validate(figment)?;
316 self.upstream_oauth2.validate(figment)?;
317
318 Ok(())
319 }
320}
321
322#[derive(Clone, Debug)]
327pub enum ClientSecret {
328 File(Utf8PathBuf),
330
331 Value(String),
333}
334
335#[derive(JsonSchema, Serialize, Deserialize, Clone, Debug)]
337pub struct ClientSecretRaw {
338 #[schemars(with = "Option<String>")]
342 #[serde(skip_serializing_if = "Option::is_none")]
343 client_secret_file: Option<Utf8PathBuf>,
344
345 #[serde(skip_serializing_if = "Option::is_none")]
348 client_secret: Option<String>,
349}
350
351impl ClientSecret {
352 pub async fn value(&self) -> anyhow::Result<String> {
360 Ok(match self {
361 ClientSecret::File(path) => tokio::fs::read_to_string(path).await?,
362 ClientSecret::Value(client_secret) => client_secret.clone(),
363 })
364 }
365}
366
367impl TryFrom<ClientSecretRaw> for Option<ClientSecret> {
368 type Error = anyhow::Error;
369
370 fn try_from(value: ClientSecretRaw) -> Result<Self, Self::Error> {
371 match (value.client_secret, value.client_secret_file) {
372 (None, None) => Ok(None),
373 (None, Some(path)) => Ok(Some(ClientSecret::File(path))),
374 (Some(client_secret), None) => Ok(Some(ClientSecret::Value(client_secret))),
375 (Some(_), Some(_)) => {
376 bail!("Cannot specify both `client_secret` and `client_secret_file`")
377 }
378 }
379 }
380}
381
382impl From<Option<ClientSecret>> for ClientSecretRaw {
383 fn from(value: Option<ClientSecret>) -> Self {
384 match value {
385 Some(ClientSecret::File(path)) => ClientSecretRaw {
386 client_secret_file: Some(path),
387 client_secret: None,
388 },
389 Some(ClientSecret::Value(client_secret)) => ClientSecretRaw {
390 client_secret_file: None,
391 client_secret: Some(client_secret),
392 },
393 None => ClientSecretRaw {
394 client_secret_file: None,
395 client_secret: None,
396 },
397 }
398 }
399}