1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
//! Configuration of the server.
use dotenvy::dotenv;
use reqwest::Url;
use secrecy::Secret;
use serde::Deserialize;
/// Environment variables that are required for the configuration of the server.
#[derive(Deserialize)]
struct EnvVars {
/// The host the server should be started on.
pub bind_address_host: String,
/// The port the server should be started on.
pub bind_address_port: u16,
/// The connection string for the database.
pub database_url: String,
/// The host of the authentication server.
pub auth_host: String,
/// The `client_id` the frontend should use to log in its users.
pub auth_client_id: String,
/// The `client_id` the backend uses to communicate with the auth server.
pub auth_admin_client_id: Option<String>,
/// The `client_secret` the backend uses to communicate with the auth server.
pub auth_admin_client_secret: Option<String>,
}
/// Configuration data for the server.
#[derive(Debug)]
pub struct Config {
/// The address and port the server should be started on.
pub bind_address: (String, u16),
/// The location of the database as a URL.
pub database_url: Secret<String>,
/// The discovery URI of the server that issues tokens.
///
/// Can be used to fetch other relevant URLs such as the `jwks_uri` or the `token_endpoint`.
pub auth_discovery_uri: Url,
/// The `client_id` the frontend should use to log in its users.
pub client_id: String,
/// The URI of the auth server used to acquire a token.
pub auth_token_uri: Url,
/// The `client_id` the backend uses to communicate with the auth server.
pub auth_admin_client_id: Option<String>,
/// The `client_secret` the backend uses to communicate with the auth server.
pub auth_admin_client_secret: Option<Secret<String>>,
}
impl Config {
/// Load the configuration using environment variables.
///
/// # Errors
/// * If the .env file is present, but there was an error loading it.
/// * If an environment variable is missing.
/// * If a variable could not be parsed correctly.
pub fn from_env() -> Result<Self, Box<dyn std::error::Error>> {
load_env_file()?;
let env: EnvVars = envy::from_env()?;
let auth_discovery_uri_str = format!(
"{}/realms/PermaplanT/.well-known/openid-configuration",
env.auth_host
);
let auth_discovery_uri = auth_discovery_uri_str.parse::<Url>().map_err(|e| {
format!("Failed to parse auth_discovery_uri: {e} (uri: {auth_discovery_uri_str})")
})?;
let auth_token_uri_str = format!(
"{}/realms/master/protocol/openid-connect/token",
env.auth_host
);
let auth_token_uri = auth_token_uri_str
.parse::<Url>()
.map_err(|e| format!("Failed to parse auth_token_uri: {e}"))?;
Ok(Self {
bind_address: (env.bind_address_host, env.bind_address_port),
database_url: Secret::new(env.database_url),
auth_discovery_uri,
client_id: env.auth_client_id,
auth_token_uri,
auth_admin_client_id: env.auth_admin_client_id,
auth_admin_client_secret: env.auth_admin_client_secret.map(Secret::new),
})
}
}
/// Load the .env file. A missing file does not result in an error.
///
/// # Errors
/// * If the .env file is present, but there was an error loading it.
fn load_env_file() -> Result<(), Box<dyn std::error::Error>> {
match dotenv() {
Err(e) if e.not_found() => Ok(()), // missing .env is ok
Err(e) => Err(e.into()), // any other errors are a problem
_ => Ok(()),
}
}