syn2mas/synapse_reader/
config.rs1use std::collections::BTreeMap;
7
8use camino::Utf8PathBuf;
9use figment::providers::{Format, Yaml};
10use serde::Deserialize;
11use sqlx::postgres::PgConnectOptions;
12
13#[derive(Deserialize)]
19#[allow(clippy::struct_excessive_bools)]
20pub struct Config {
21 pub database: DatabaseSection,
22
23 #[serde(default)]
24 pub password_config: PasswordSection,
25
26 #[serde(default)]
27 pub allow_guest_access: bool,
28
29 #[serde(default)]
30 pub enable_registration: bool,
31
32 #[serde(default)]
33 pub enable_registration_captcha: bool,
34
35 #[serde(default)]
38 pub enable_3pid_changes: bool,
39
40 #[serde(default)]
41 pub user_consent: Option<UserConsentSection>,
42
43 #[serde(default)]
44 pub registrations_require_3pid: Vec<String>,
45
46 #[serde(default)]
47 pub registration_requires_token: bool,
48
49 pub registration_shared_secret: Option<String>,
50
51 #[serde(default)]
52 pub login_via_existing_session: EnableableSection,
53
54 #[serde(default)]
55 pub cas_config: EnableableSection,
56
57 #[serde(default)]
58 pub saml2_config: EnableableSection,
59
60 #[serde(default)]
61 pub jwt_config: EnableableSection,
62
63 #[serde(default)]
64 pub oidc_config: Option<OidcProvider>,
65
66 #[serde(default)]
67 pub oidc_providers: Vec<OidcProvider>,
68
69 pub server_name: String,
70}
71
72impl Config {
73 pub fn load(files: &[Utf8PathBuf]) -> Result<Config, figment::Error> {
80 let mut figment = figment::Figment::new();
81 for file in files {
82 figment = figment.merge(Yaml::file(file));
87 }
88 figment.extract::<Config>()
89 }
90
91 #[must_use]
99 pub fn all_oidc_providers(&self) -> BTreeMap<String, OidcProvider> {
100 let mut out = BTreeMap::new();
101
102 if let Some(provider) = &self.oidc_config {
103 if provider.issuer.is_some() {
104 out.insert("oidc".to_owned(), provider.clone());
106 }
107 }
108
109 for provider in &self.oidc_providers {
110 if let Some(idp_id) = &provider.idp_id {
111 out.insert(format!("oidc-{idp_id}"), provider.clone());
113 }
114 }
115
116 out
117 }
118}
119
120#[derive(Deserialize)]
124pub struct DatabaseSection {
125 pub name: String,
129 #[serde(default)]
130 pub args: DatabaseArgsSuboption,
131}
132
133pub const SYNAPSE_DATABASE_DRIVER_NAME_PSYCOPG2: &str = "psycopg2";
135pub const SYNAPSE_DATABASE_DRIVER_NAME_SQLITE3: &str = "sqlite3";
137
138impl DatabaseSection {
139 #[must_use]
150 pub fn to_sqlx_postgres(&self) -> Option<PgConnectOptions> {
151 if self.name != SYNAPSE_DATABASE_DRIVER_NAME_PSYCOPG2 {
152 return None;
153 }
154 let mut opts = PgConnectOptions::new().application_name("syn2mas-synapse");
155
156 if let Some(host) = &self.args.host {
157 opts = opts.host(host);
158 }
159 if let Some(port) = self.args.port {
160 opts = opts.port(port);
161 }
162 if let Some(dbname) = &self.args.dbname {
163 opts = opts.database(dbname);
164 }
165 if let Some(user) = &self.args.user {
166 opts = opts.username(user);
167 }
168 if let Some(password) = &self.args.password {
169 opts = opts.password(password);
170 }
171
172 Some(opts)
173 }
174}
175
176#[derive(Deserialize, Default)]
180pub struct DatabaseArgsSuboption {
181 pub user: Option<String>,
182 pub password: Option<String>,
183 pub dbname: Option<String>,
184 pub host: Option<String>,
185 pub port: Option<u16>,
186}
187
188#[derive(Deserialize)]
192pub struct PasswordSection {
193 #[serde(default = "default_true")]
194 pub enabled: bool,
195 #[serde(default = "default_true")]
196 pub localdb_enabled: bool,
197 pub pepper: Option<String>,
198}
199
200impl Default for PasswordSection {
201 fn default() -> Self {
202 PasswordSection {
203 enabled: true,
204 localdb_enabled: true,
205 pepper: None,
206 }
207 }
208}
209
210#[derive(Default, Deserialize)]
213pub struct EnableableSection {
214 #[serde(default)]
215 pub enabled: bool,
216}
217
218#[derive(Clone, Deserialize)]
219pub struct OidcProvider {
220 pub issuer: Option<String>,
223
224 pub idp_id: Option<String>,
227}
228
229fn default_true() -> bool {
230 true
231}
232
233#[cfg(test)]
234mod test {
235 use sqlx::postgres::PgConnectOptions;
236
237 use super::{DatabaseArgsSuboption, DatabaseSection};
238
239 #[test]
240 fn test_to_sqlx_postgres() {
241 #[track_caller]
242 #[allow(clippy::needless_pass_by_value)]
243 fn assert_eq_options(config: DatabaseSection, uri: &str) {
244 let config_connect_options = config
245 .to_sqlx_postgres()
246 .expect("no connection options generated by config");
247 let uri_connect_options: PgConnectOptions = uri
248 .parse()
249 .expect("example URI did not parse as PgConnectionOptions");
250
251 assert_eq!(
252 config_connect_options.get_host(),
253 uri_connect_options.get_host()
254 );
255 assert_eq!(
256 config_connect_options.get_port(),
257 uri_connect_options.get_port()
258 );
259 assert_eq!(
260 config_connect_options.get_username(),
261 uri_connect_options.get_username()
262 );
263 assert_eq!(
265 config_connect_options.get_database(),
266 uri_connect_options.get_database()
267 );
268 }
269
270 assert!(
272 DatabaseSection {
273 name: "sqlite3".to_owned(),
274 args: DatabaseArgsSuboption::default(),
275 }
276 .to_sqlx_postgres()
277 .is_none()
278 );
279
280 assert_eq_options(
281 DatabaseSection {
282 name: "psycopg2".to_owned(),
283 args: DatabaseArgsSuboption::default(),
284 },
285 "postgresql:///",
286 );
287 assert_eq_options(
288 DatabaseSection {
289 name: "psycopg2".to_owned(),
290 args: DatabaseArgsSuboption {
291 user: Some("synapse_user".to_owned()),
292 password: Some("verysecret".to_owned()),
293 dbname: Some("synapse_db".to_owned()),
294 host: Some("synapse-db.example.com".to_owned()),
295 port: Some(42),
296 },
297 },
298 "postgresql://synapse_user:verysecret@synapse-db.example.com:42/synapse_db",
299 );
300 }
301}
302
303#[derive(Deserialize)]
306pub struct UserConsentSection {}