backend/config/app.rs
1//! Configuration of the server.
2
3use dotenvy::dotenv;
4use reqwest::Url;
5use secrecy::Secret;
6use serde::Deserialize;
7
8/// Environment variables that are required for the configuration of the server.
9#[derive(Deserialize)]
10struct EnvVars {
11 /// The host the server should be started on.
12 pub bind_address_host: String,
13 /// The port the server should be started on.
14 pub bind_address_port: u16,
15 /// The connection string for the database.
16 pub database_url: String,
17 /// The host of the authentication server.
18 pub auth_host: String,
19 /// The `client_id` the frontend should use to log in its users.
20 pub auth_client_id: String,
21 /// The `client_id` the backend uses to communicate with the auth server.
22 pub auth_admin_client_id: Option<String>,
23 /// The `client_secret` the backend uses to communicate with the auth server.
24 pub auth_admin_client_secret: Option<String>,
25 /// The `mode` of Access Control that is used.
26 pub mode: String,
27}
28
29/// Configuration data for the server.
30#[derive(Debug)]
31pub struct Config {
32 /// The address and port the server should be started on.
33 pub bind_address: (String, u16),
34 /// The location of the database as a URL.
35 pub database_url: Secret<String>,
36 /// The discovery URI of the server that issues tokens.
37 ///
38 /// Can be used to fetch other relevant URLs such as the `jwks_uri` or the `token_endpoint`.
39 pub auth_discovery_uri: Url,
40 /// The `client_id` the frontend should use to log in its users.
41 pub client_id: String,
42
43 /// The URI of the auth server used to acquire a token.
44 pub auth_token_uri: Url,
45 /// The `client_id` the backend uses to communicate with the auth server.
46 pub auth_admin_client_id: Option<String>,
47 /// The `client_secret` the backend uses to communicate with the auth server.
48 pub auth_admin_client_secret: Option<Secret<String>>,
49 /// The `mode` of Access Control that is used.
50 pub mode: Mode,
51}
52
53/// Modes that the backend can be run in.
54#[derive(Debug, Clone, Copy, Deserialize)]
55pub enum Mode {
56 /// This mode indicates that the backend is run in a production environment.
57 Production,
58 /// This mode indicates that the backend is run in a testing environment.
59 Testing,
60}
61
62impl Mode {
63 ///
64 /// # Errors
65 /// * If the supplied String does not match any mode.
66 pub fn from_string(str: &str) -> Result<Self, &'static str> {
67 match str {
68 "testing" => Ok(Self::Testing),
69 "production" => {
70 if cfg!(not(feature = "access_control")) {
71 return Err("feature access_control is disabled but mode production is set");
72 }
73 Ok(Self::Production)
74 }
75 _ => Err("invalid MODE in .env, should be testing or production"),
76 }
77 }
78}
79
80impl Config {
81 /// Load the configuration using environment variables.
82 ///
83 /// # Errors
84 /// * If the .env file is present, but there was an error loading it.
85 /// * If an environment variable is missing.
86 /// * If a variable could not be parsed correctly.
87 pub fn from_env() -> Result<Self, Box<dyn std::error::Error>> {
88 load_env_file()?;
89 let env: EnvVars = envy::from_env()?;
90
91 let auth_discovery_uri_str = format!(
92 "{}/realms/PermaplanT/.well-known/openid-configuration",
93 env.auth_host
94 );
95 let auth_discovery_uri = auth_discovery_uri_str.parse::<Url>().map_err(|e| {
96 format!("Failed to parse auth_discovery_uri: {e} (uri: {auth_discovery_uri_str})")
97 })?;
98 let auth_token_uri_str = format!(
99 "{}/realms/master/protocol/openid-connect/token",
100 env.auth_host
101 );
102 let auth_token_uri = auth_token_uri_str
103 .parse::<Url>()
104 .map_err(|e| format!("Failed to parse auth_token_uri: {e}"))?;
105
106 let mode = Mode::from_string(&env.mode).map_err(|err| format!("Error {err}"))?;
107
108 Ok(Self {
109 bind_address: (env.bind_address_host, env.bind_address_port),
110 database_url: Secret::new(env.database_url),
111 auth_discovery_uri,
112 client_id: env.auth_client_id,
113 auth_token_uri,
114 auth_admin_client_id: env.auth_admin_client_id,
115 auth_admin_client_secret: env.auth_admin_client_secret.map(Secret::new),
116 mode,
117 })
118 }
119}
120
121/// Load the .env file. A missing file does not result in an error.
122///
123/// # Errors
124/// * If the .env file is present, but there was an error loading it.
125fn load_env_file() -> Result<(), Box<dyn std::error::Error>> {
126 match dotenv() {
127 Err(e) if e.not_found() => Ok(()), // missing .env is ok
128 Err(e) => Err(e.into()), // any other errors are a problem
129 _ => Ok(()),
130 }
131}