mcaptcha/
settings.rs

1// Copyright (C) 2022  Aravinth Manivannan <realaravinth@batsense.net>
2// SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
3//
4// SPDX-License-Identifier: AGPL-3.0-or-later
5
6use std::path::Path;
7use std::{env, fs};
8
9use config::builder::DefaultState;
10use config::{Config, ConfigBuilder, ConfigError, File};
11use derive_more::Display;
12
13use serde::{Deserialize, Serialize};
14use url::Url;
15
16#[derive(Debug, Clone, Deserialize, Eq, PartialEq)]
17pub struct Server {
18    pub port: u32,
19    pub domain: String,
20    pub cookie_secret: String,
21    pub ip: String,
22    // TODO: remove
23    pub url_prefix: Option<String>,
24    pub proxy_has_tls: bool,
25}
26
27#[derive(Debug, Clone, Deserialize, Eq, PartialEq)]
28pub struct Captcha {
29    pub salt: String,
30    pub gc: u64,
31    pub runners: Option<usize>,
32    pub queue_length: usize,
33    pub enable_stats: bool,
34    pub default_difficulty_strategy: DefaultDifficultyStrategy,
35}
36
37#[derive(Debug, Clone, Deserialize, Eq, PartialEq)]
38pub struct DefaultDifficultyStrategy {
39    pub avg_traffic_difficulty: u32,
40    pub avg_traffic_time: Option<u32>,
41    pub peak_sustainable_traffic_difficulty: u32,
42    pub peak_sustainable_traffic_time: Option<u32>,
43    pub broke_my_site_traffic_time: Option<u32>,
44    pub broke_my_site_traffic_difficulty: u32,
45    pub duration: u32,
46}
47
48#[derive(Debug, Clone, Deserialize, Eq, PartialEq)]
49pub struct Smtp {
50    pub from: String,
51    pub reply: String,
52    pub url: String,
53    pub username: String,
54    pub password: String,
55    pub port: u16,
56}
57
58impl Server {
59    pub fn get_ip(&self) -> String {
60        format!("{}:{}", self.ip, self.port)
61    }
62}
63
64#[derive(Deserialize, Serialize, Display, Eq, PartialEq, Clone, Debug)]
65#[serde(rename_all = "lowercase")]
66pub enum DBType {
67    #[display(fmt = "postgres")]
68    Postgres,
69    #[display(fmt = "maria")]
70    Maria,
71}
72
73impl DBType {
74    fn from_url(url: &Url) -> Result<Self, ConfigError> {
75        match url.scheme() {
76            "mysql" => Ok(Self::Maria),
77            "postgres" => Ok(Self::Postgres),
78            _ => Err(ConfigError::Message("Unknown database type".into())),
79        }
80    }
81}
82
83#[derive(Debug, Clone, Deserialize, Eq, PartialEq)]
84pub struct Database {
85    pub url: String,
86    pub pool: u32,
87    pub database_type: DBType,
88}
89
90#[derive(Debug, Clone, Deserialize, Eq, PartialEq)]
91pub struct Redis {
92    pub url: String,
93    pub pool: u32,
94}
95
96#[derive(Debug, Clone, Deserialize, Eq, PartialEq)]
97pub struct Survey {
98    pub nodes: Vec<url::Url>,
99    pub rate_limit: u64,
100    pub instance_root_url: Url,
101}
102
103#[derive(Debug, Clone, Deserialize, Eq, PartialEq)]
104pub struct Settings {
105    pub debug: bool,
106    pub commercial: bool,
107    pub source_code: String,
108    pub allow_registration: bool,
109    pub allow_demo: bool,
110    pub database: Database,
111    pub survey: Option<Survey>,
112    pub redis: Option<Redis>,
113    pub server: Server,
114    pub captcha: Captcha,
115    pub smtp: Option<Smtp>,
116}
117
118const ENV_VAR_CONFIG: [(&str, &str); 32] = [
119    /* top-level */
120    ("debug", "MCAPTCHA_debug"),
121    ("commercial", "MCAPTCHA_commercial"),
122    ("source_code", "MCAPTCHA_source_code"),
123    ("allow_registration", "MCAPTCHA_allow_registration"),
124    ("allow_demo", "MCAPTCHA_allow_demo"),
125
126    /* database */
127    ("database.url", "DATABASE_URL"),
128    ("database.pool", "MCAPTCHA_database_POOL"),
129
130    /* redis */
131    ("redis.url", "MCAPTCHA_redis_URL"),
132    ("redis.pool", "MCAPTCHA_redis_POOL"),
133
134    /* server */
135    ("server.port", "PORT"),
136    ("server.domain", "MCAPTCHA_server_DOMAIN"),
137    ("server.cookie_secret", "MCAPTCHA__server_COOKIE_SECRET"),
138    ("server.ip", "MCAPTCHA__server_IP"),
139    ("server.proxy_has_tls", "MCAPTCHA__server_PROXY_HAS_TLS"),
140
141
142    /* captcha */
143    ("captcha.salt", "MCAPTCHA_captcha_SALT"),
144    ("captcha.gc", "MCAPTCHA_captcha_GC"),
145    ("captcha.runners", "MCAPTCHA_captcha_RUNNERS"),
146    ("captcha.queue_length", "MCAPTCHA_captcha_QUEUE_LENGTH"),
147    ("captcha.enable_stats", "MCAPTCHA_captcha_ENABLE_STATS"),
148    ("captcha.default_difficulty_strategy.avg_traffic_difficulty", "MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_avg_traffic_difficulty"),
149    ("captcha.default_difficulty_strategy.broke_my_site_traffic_difficulty", "MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_broke_my_site_traffic_difficulty"),
150    ("captcha.default_difficulty_strategy.peak_sustainable_traffic_difficulty",
151     "MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_peak_sustainable_traffic_difficulty"),
152    ( "captcha.default_difficulty_strategy.duration",
153         "MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_duration"
154     ),
155    ("captcha.default_difficulty_strategy.avg_traffic_time", "MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_avg_traffic_time"),
156    ("captcha.default_difficulty_strategy.peak_sustainable_traffic_time", "MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_peak_sustainable_traffic_time"),
157    ("captcha.default_difficulty_strategy.broke_my_site_traffic_time", "MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_broke_my_site_traffic_time"),
158
159
160    /* SMTP */
161    ("smtp.from", "MCAPTCHA_smtp_FROM"),
162    ("smtp.reply", "MCAPTCHA_smtp_REPLY"),
163    ("smtp.url", "MCAPTCHA_smtp_URL"),
164    ("smtp.username", "MCAPTCHA_smtp_USERNAME"),
165    ("smtp.password", "MCAPTCHA_smtp_PASSWORD"),
166    ("smtp.port", "MCAPTCHA_smtp_PORT"),
167
168
169
170];
171
172const DEPRECATED_ENV_VARS: [(&str, &str); 23] = [
173    ("debug", "MCAPTCHA_DEBUG"),
174    ("commercial", "MCAPTCHA_COMMERCIAL"),
175    ("source_code", "MCAPTCHA_SOURCE_CODE"),
176    ("allow_registration", "MCAPTCHA_ALLOW_REGISTRATION"),
177    ("allow_demo", "MCAPTCHA_ALLOW_DEMO"),
178    ("redis.pool", "MCAPTCHA_REDIS_POOL"),
179    ("redis.url", "MCAPTCHA_REDIS_URL"),
180    ("server.port", "MCAPTCHA_SERVER_PORT"),
181    ("server.ip", "MCAPTCHA_SERVER_IP"),
182    ("server.domain", "MCAPTCHA_SERVER_DOMAIN"),
183    ("server.cookie_secret", "MCAPTCHA_SERVER_COOKIE_SECRET"),
184    ("server.proxy_has_tls", "MCAPTCHA_SERVER_PROXY_HAS_TLS"),
185    ("captcha.salt", "MCAPTCHA_CAPTCHA_SALT"),
186    ("captcha.gc", "MCAPTCHA_CAPTCHA_GC"),
187    (
188        "captcha.default_difficulty_strategy.avg_traffic_difficulty",
189        "MCAPTCHA_CAPTCHA_AVG_TRAFFIC_DIFFICULTY",
190    ),
191    (
192        "captcha.default_difficulty_strategy.peak_sustainable_traffic_difficulty",
193        "MCAPTCHA_CAPTCHA_PEAK_TRAFFIC_DIFFICULTY",
194    ),
195    (
196        "captcha.default_difficulty_strategy.broke_my_site_traffic_difficulty",
197        "MCAPTCHA_CAPTCHA_BROKE_MY_SITE_TRAFFIC",
198    ),
199    ("smtp.from", "MCAPTCHA_SMTP_FROM"),
200    ("smtp.reply", "MCAPTCHA_SMTP_REPLY_TO"),
201    ("smtp.url", "MCAPTCHA_SMTP_URL"),
202    ("smtp.username", "MCAPTCHA_SMTP_USERNAME"),
203    ("smtp.password", "MCAPTCHA_SMTP_PASSWORD"),
204    ("smtp.port", "MCAPTCHA_SMTP_PORT"),
205];
206
207impl Settings {
208    pub fn new() -> Result<Self, ConfigError> {
209        let mut s = Config::builder();
210
211        const CURRENT_DIR: &str = "./config/default.toml";
212        const ETC: &str = "/etc/mcaptcha/config.toml";
213
214        s = s
215            .set_default("capatcha.enable_stats", true.to_string())
216            .expect("unable to set capatcha.enable_stats default config");
217
218        // Will be overridden after config is parsed and loaded into Settings by
219        // Settings::set_database_type.
220        // This parameter is not ergonomic for users, but it is required and can be programatically
221        // inferred. But we need a default value for config lib to parse successfully, since it is
222        // DBType and not Option<DBType>
223        s = s
224            .set_default("database.database_type", DBType::Postgres.to_string())
225            .expect("unable to set database.database_type default config");
226
227        if let Ok(path) = env::var("MCAPTCHA_CONFIG") {
228            let absolute_path = Path::new(&path).canonicalize().unwrap();
229            log::info!(
230                "Loading config file from {}",
231                absolute_path.to_str().unwrap()
232            );
233            s = s.add_source(File::with_name(absolute_path.to_str().unwrap()));
234        } else if Path::new(CURRENT_DIR).exists() {
235            let absolute_path = fs::canonicalize(CURRENT_DIR).unwrap();
236            log::info!(
237                "Loading config file from {}",
238                absolute_path.to_str().unwrap()
239            );
240            // merging default config from file
241            s = s.add_source(File::with_name(absolute_path.to_str().unwrap()));
242        } else if Path::new(ETC).exists() {
243            log::info!("{}", format!("Loading config file from {}", ETC));
244            s = s.add_source(File::with_name(ETC));
245        } else {
246            log::warn!("Configuration file not found");
247        }
248
249        s = Self::env_override(s);
250
251        let mut settings = s.build()?.try_deserialize::<Settings>()?;
252        settings.check_url();
253
254        settings.set_database_type();
255
256        Ok(settings)
257    }
258    fn check_easy_captcha_config(&self) {
259        let s = &self.captcha.default_difficulty_strategy;
260        if s.avg_traffic_time.is_some() {
261            if s.broke_my_site_traffic_time.is_none()
262                || s.peak_sustainable_traffic_time.is_none()
263            {
264                panic!("if captcha.default_difficulty_strategy.avg_traffic_time is set, then captcha.default_difficulty_strategy.broke_my_site_traffic_time and captcha.default_difficulty_strategy.peak_sustainable_traffic_time must also be set");
265            }
266        }
267        if s.peak_sustainable_traffic_time.is_some() {
268            if s.avg_traffic_time.is_none() || s.peak_sustainable_traffic_time.is_none()
269            {
270                panic!("if captcha.default_difficulty_strategy.peak_sustainable_traffic_time is set, then captcha.default_difficulty_strategy.broke_my_site_traffic_time and captcha.default_difficulty_strategy.avg_traffic_time must also be set");
271            }
272        }
273        if s.broke_my_site_traffic_time.is_some() {
274            if s.avg_traffic_time.is_none() || s.peak_sustainable_traffic_time.is_none()
275            {
276                panic!("if captcha.default_difficulty_strategy.broke_my_site_traffic_time is set, then captcha.default_difficulty_strategy.peak_sustainable_traffic_time and captcha.default_difficulty_strategy.avg_traffic_time must also be set");
277            }
278        }
279    }
280
281    fn env_override(mut s: ConfigBuilder<DefaultState>) -> ConfigBuilder<DefaultState> {
282        for (parameter, env_var_name) in DEPRECATED_ENV_VARS.iter() {
283            if let Ok(val) = env::var(env_var_name) {
284                log::warn!(
285                    "Found {env_var_name}. {env_var_name} will be deprecated soon. Please see https://github.com/mCaptcha/mCaptcha/blob/master/docs/CONFIGURATION.md for latest environment variable names"
286                );
287                s = s.set_override(parameter, val).unwrap();
288            }
289        }
290
291        for (parameter, env_var_name) in ENV_VAR_CONFIG.iter() {
292            if let Ok(val) = env::var(env_var_name) {
293                log::debug!(
294                    "Overriding [{parameter}] with environment variable {env_var_name}"
295                );
296                s = s.set_override(parameter, val).unwrap();
297            }
298        }
299
300        s
301    }
302
303    fn set_database_type(&mut self) {
304        let url = Url::parse(&self.database.url)
305            .expect("couldn't parse Database URL and detect database type");
306        self.database.database_type = DBType::from_url(&url).unwrap();
307    }
308
309    fn check_url(&self) {
310        Url::parse(&self.source_code)
311            .expect("Please enter a URL for source_code in settings");
312    }
313}
314
315#[cfg(test)]
316mod tests {
317
318    use super::*;
319
320    #[test]
321    fn deprecated_env_override_works() {
322        use crate::tests::get_settings;
323        let init_settings = get_settings();
324        // so that it can be tested outside the macro (helper) too
325        let mut new_settings;
326
327        macro_rules! helper {
328
329
330            ($env:expr, $val:expr, $val_typed:expr, $($param:ident).+) => {
331                println!("Setting env var {} to {} for test", $env, $val);
332                env::set_var($env, $val);
333                new_settings = get_settings();
334                assert_eq!(new_settings.$($param).+, $val_typed);
335                assert_ne!(new_settings.$($param).+, init_settings.$($param).+);
336                env::remove_var($env);
337            };
338
339
340            ($env:expr, $val:expr, $($param:ident).+) => {
341                helper!($env, $val.to_string(), $val, $($param).+);
342            };
343        }
344
345        /* top level */
346        helper!("MCAPTCHA_DEBUG", !init_settings.debug, debug);
347        helper!("MCAPTCHA_COMMERCIAL", !init_settings.commercial, commercial);
348        helper!(
349            "MCAPTCHA_ALLOW_REGISTRATION",
350            !init_settings.allow_registration,
351            allow_registration
352        );
353        helper!("MCAPTCHA_ALLOW_DEMO", !init_settings.allow_demo, allow_demo);
354
355        /* database_type */
356
357        /* redis.url */
358        let env = "MCAPTCHA_REDIS_URL";
359        let val = "redis://redis.example.org";
360        println!("Setting env var {} to {} for test", env, val);
361        env::set_var(env, val);
362        new_settings = get_settings();
363        assert_eq!(new_settings.redis.as_ref().unwrap().url, val);
364        assert_ne!(
365            new_settings.redis.as_ref().unwrap().url,
366            init_settings.redis.as_ref().unwrap().url
367        );
368        env::remove_var(env);
369
370        /* redis.pool */
371        let env = "MCAPTCHA_REDIS_POOL";
372        let val = 999;
373        println!("Setting env var {} to {} for test", env, val);
374        env::set_var(env, val.to_string());
375        new_settings = get_settings();
376        assert_eq!(new_settings.redis.as_ref().unwrap().pool, val);
377        assert_ne!(
378            new_settings.redis.as_ref().unwrap().pool,
379            init_settings.redis.as_ref().unwrap().pool
380        );
381        env::remove_var(env);
382
383        helper!("PORT", 0, server.port);
384        helper!("MCAPTCHA_SERVER_DOMAIN", "example.org", server.domain);
385        helper!(
386            "MCAPTCHA_SERVER_COOKIE_SECRET",
387            "dafasdfsdf",
388            server.cookie_secret
389        );
390        helper!("MCAPTCHA_SERVER_IP", "9.9.9.9", server.ip);
391        helper!("MCAPTCHA_SERVER_PROXY_HAS_TLS", true, server.proxy_has_tls);
392
393        /* captcha */
394
395        helper!("MCAPTCHA_CAPTCHA_SALT", "foobarasdfasdf", captcha.salt);
396        helper!("MCAPTCHA_CAPTCHA_GC", 500, captcha.gc);
397        helper!(
398            "MCAPTCHA_captcha_RUNNERS",
399            "500",
400            Some(500),
401            captcha.runners
402        );
403
404        helper!(
405            "MCAPTCHA_CAPTCHA_AVG_TRAFFIC_DIFFICULTY",
406            999,
407            captcha.default_difficulty_strategy.avg_traffic_difficulty
408        );
409        helper!(
410            "MCAPTCHA_CAPTCHA_PEAK_TRAFFIC_DIFFICULTY",
411            999,
412            captcha
413                .default_difficulty_strategy
414                .peak_sustainable_traffic_difficulty
415        );
416        helper!(
417            "MCAPTCHA_CAPTCHA_BROKE_MY_SITE_TRAFFIC",
418            999,
419            captcha
420                .default_difficulty_strategy
421                .broke_my_site_traffic_difficulty
422        );
423
424        /* SMTP */
425
426        let vals = [
427            "MCAPTCHA_SMTP_FROM",
428            "MCAPTCHA_SMTP_REPLY_TO",
429            "MCAPTCHA_SMTP_URL",
430            "MCAPTCHA_SMTP_USERNAME",
431            "MCAPTCHA_SMTP_PASSWORD",
432            "MCAPTCHA_SMTP_PORT",
433        ];
434        for env in vals.iter() {
435            println!("Setting env var {} to {} for test", env, env);
436            env::set_var(env, env);
437        }
438
439        let port = 9999;
440        env::set_var("MCAPTCHA_SMTP_PORT", port.to_string());
441
442        new_settings = get_settings();
443        let smtp_new = new_settings.smtp.as_ref().unwrap();
444        let smtp_old = init_settings.smtp.as_ref().unwrap();
445        assert_eq!(smtp_new.from, "MCAPTCHA_SMTP_FROM");
446        assert_eq!(smtp_new.reply, "MCAPTCHA_SMTP_REPLY_TO");
447        assert_eq!(smtp_new.username, "MCAPTCHA_SMTP_USERNAME");
448        assert_eq!(smtp_new.password, "MCAPTCHA_SMTP_PASSWORD");
449        assert_eq!(smtp_new.port, port);
450        assert_ne!(smtp_new, smtp_old);
451
452        for env in vals.iter() {
453            env::remove_var(env);
454        }
455    }
456
457    #[test]
458    fn env_override_works() {
459        use crate::tests::get_settings;
460        let init_settings = get_settings();
461        // so that it can be tested outside the macro (helper) too
462        let mut new_settings;
463
464        macro_rules! helper {
465
466
467            ($env:expr, $val:expr, $val_typed:expr, $($param:ident).+) => {
468                println!("Setting env var {} to {} for test", $env, $val);
469                env::set_var($env, $val);
470                new_settings = get_settings();
471                assert_eq!(new_settings.$($param).+, $val_typed);
472                assert_ne!(new_settings.$($param).+, init_settings.$($param).+);
473                env::remove_var($env);
474            };
475
476
477            ($env:expr, $val:expr, $($param:ident).+) => {
478                helper!($env, $val.to_string(), $val, $($param).+);
479            };
480        }
481
482        /* top level */
483        helper!("MCAPTCHA_debug", false, debug);
484        helper!("MCAPTCHA_commercial", true, commercial);
485        helper!("MCAPTCHA_allow_registration", false, allow_registration);
486        helper!("MCAPTCHA_allow_demo", false, allow_demo);
487
488        /* database_type */
489
490        helper!(
491            "DATABASE_URL",
492            "postgres://postgres:password@localhost:5432/postgres",
493            database.url
494        );
495        assert_eq!(new_settings.database.database_type, DBType::Postgres);
496        helper!(
497            "DATABASE_URL",
498            "mysql://maria:password@localhost/maria",
499            database.url
500        );
501        assert_eq!(new_settings.database.database_type, DBType::Maria);
502        helper!("MCAPTCHA_database_POOL", 1000, database.pool);
503
504        /* redis */
505
506        /* redis.url */
507        let env = "MCAPTCHA_redis_URL";
508        let val = "redis://redis.example.org";
509        println!("Setting env var {} to {} for test", env, val);
510        env::set_var(env, val);
511        new_settings = get_settings();
512        assert_eq!(new_settings.redis.as_ref().unwrap().url, val);
513        assert_ne!(
514            new_settings.redis.as_ref().unwrap().url,
515            init_settings.redis.as_ref().unwrap().url
516        );
517        env::remove_var(env);
518
519        /* redis.pool */
520        let env = "MCAPTCHA_redis_POOL";
521        let val = 999;
522        println!("Setting env var {} to {} for test", env, val);
523        env::set_var(env, val.to_string());
524        new_settings = get_settings();
525        assert_eq!(new_settings.redis.as_ref().unwrap().pool, val);
526        assert_ne!(
527            new_settings.redis.as_ref().unwrap().pool,
528            init_settings.redis.as_ref().unwrap().pool
529        );
530        env::remove_var(env);
531
532        helper!("PORT", 0, server.port);
533        helper!("MCAPTCHA_server_DOMAIN", "example.org", server.domain);
534        helper!(
535            "MCAPTCHA__server_COOKIE_SECRET",
536            "dafasdfsdf",
537            server.cookie_secret
538        );
539        helper!("MCAPTCHA__server_IP", "9.9.9.9", server.ip);
540        helper!("MCAPTCHA__server_PROXY_HAS_TLS", true, server.proxy_has_tls);
541
542        /* captcha */
543
544        helper!("MCAPTCHA_captcha_SALT", "foobarasdfasdf", captcha.salt);
545        helper!("MCAPTCHA_captcha_GC", 500, captcha.gc);
546        helper!(
547            "MCAPTCHA_captcha_RUNNERS",
548            "500",
549            Some(500),
550            captcha.runners
551        );
552
553        helper!("MCAPTCHA_captcha_QUEUE_LENGTH", 500, captcha.queue_length);
554        helper!("MCAPTCHA_captcha_ENABLE_STATS", false, captcha.enable_stats);
555        helper!(
556            "MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_avg_traffic_difficulty",
557            999,
558            captcha.default_difficulty_strategy.avg_traffic_difficulty
559        );
560        helper!("MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_peak_sustainable_traffic_difficulty", 999 , captcha.default_difficulty_strategy.peak_sustainable_traffic_difficulty);
561        helper!("MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_broke_my_site_traffic_difficulty", 999 , captcha.default_difficulty_strategy.broke_my_site_traffic_difficulty);
562        helper!(
563            "MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_duration",
564            999,
565            captcha.default_difficulty_strategy.duration
566        );
567        helper!(
568            "MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_avg_traffic_time",
569            "10",
570            Some(10),
571            captcha.default_difficulty_strategy.avg_traffic_time
572        );
573
574        helper!(
575            "MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_peak_sustainable_traffic_time",
576            "20",
577            Some(20),
578            captcha
579                .default_difficulty_strategy
580                .peak_sustainable_traffic_time
581        );
582
583        helper!(
584            "MCAPTCHA_captcha_DEFAULT_DIFFICULTY_STRATEGY_broke_my_site_traffic_time",
585            "30",
586            Some(30),
587            captcha
588                .default_difficulty_strategy
589                .broke_my_site_traffic_time
590        );
591
592        /* SMTP */
593
594        let vals = [
595            "MCAPTCHA_smtp_FROM",
596            "MCAPTCHA_smtp_REPLY",
597            "MCAPTCHA_smtp_URL",
598            "MCAPTCHA_smtp_USERNAME",
599            "MCAPTCHA_smtp_PASSWORD",
600            "MCAPTCHA_smtp_PORT",
601        ];
602        for env in vals.iter() {
603            println!("Setting env var {} to {} for test", env, env);
604            env::set_var(env, env);
605        }
606
607        let port = 9999;
608        env::set_var("MCAPTCHA_smtp_PORT", port.to_string());
609
610        new_settings = get_settings();
611        let smtp_new = new_settings.smtp.as_ref().unwrap();
612        let smtp_old = init_settings.smtp.as_ref().unwrap();
613        assert_eq!(smtp_new.from, "MCAPTCHA_smtp_FROM");
614        assert_eq!(smtp_new.reply, "MCAPTCHA_smtp_REPLY");
615        assert_eq!(smtp_new.username, "MCAPTCHA_smtp_USERNAME");
616        assert_eq!(smtp_new.password, "MCAPTCHA_smtp_PASSWORD");
617        assert_eq!(smtp_new.port, port);
618        assert_ne!(smtp_new, smtp_old);
619
620        for env in vals.iter() {
621            env::remove_var(env);
622        }
623    }
624
625    //    #[test]
626    //    fn url_prefix_test() {
627    //        let mut settings = Settings::new().unwrap();
628    //        assert!(settings.server.url_prefix.is_none());
629    //        settings.server.url_prefix = Some("test".into());
630    //        settings.server.check_url_prefix();
631    //        settings.server.url_prefix = Some("    ".into());
632    //        settings.server.check_url_prefix();
633    //        assert!(settings.server.url_prefix.is_none());
634    //    }
635    //
636    //    #[test]
637    //    fn smtp_config_works() {
638    //        let settings = Settings::new().unwrap();
639    //        assert!(settings.smtp.is_some());
640    //        assert_eq!(settings.smtp.as_ref().unwrap().password, "password");
641    //        assert_eq!(settings.smtp.as_ref().unwrap().username, "admin");
642    //    }
643}