Skip to main content

web/
config.rs

1use server::config::{ConfigError, system_config};
2
3fn get_env_var(var_name: &str) -> String {
4    std::env::var(var_name).unwrap_or_else(|_| panic!("{var_name} must be set"))
5}
6
7/// Public-facing SSH endpoint advertised in the
8/// `/account/ssh-key` "key added" snippet. All three values are
9/// optional — operators set them via env (`SSH_HOSTNAME`,
10/// `SSH_PORT`, `SSH_HOST_FINGERPRINT`) on the deployment ConfigMap.
11#[derive(Debug, Clone)]
12pub struct SshConnectInfo {
13    pub hostname: String,
14    pub port: u16,
15    pub host_fingerprint: String,
16}
17
18impl SshConnectInfo {
19    fn from_env() -> Self {
20        Self {
21            hostname: std::env::var("SSH_HOSTNAME")
22                .unwrap_or_else(|_| "ssh.example.invalid".to_string()),
23            port: std::env::var("SSH_PORT")
24                .ok()
25                .and_then(|raw| raw.parse::<u16>().ok())
26                .unwrap_or(2222),
27            host_fingerprint: std::env::var("SSH_HOST_FINGERPRINT")
28                .unwrap_or_else(|_| "(not configured)".to_string()),
29        }
30    }
31}
32
33#[derive(Debug, Clone)]
34pub struct Config {
35    pub site_url: String,
36
37    pub redis_url: String,
38    pub client_origin: String,
39
40    // Token lifetimes only. Signing keys are no longer global config — each user
41    // signs with their own keypair (see `crate::auth_keys` / `server::auth_keys`).
42    pub access_token_expires_in: String,
43    pub access_token_max_age: i64,
44
45    pub refresh_token_expires_in: String,
46    pub refresh_token_max_age: i64,
47
48    pub ssh: SshConnectInfo,
49}
50
51impl Config {
52    pub async fn init() -> Result<Config, ConfigError> {
53        let site_url = system_config("site_url")
54            .await?
55            .ok_or(ConfigError::NoConfig("site_url".to_string()))?
56            .to_string();
57
58        let client_origin = system_config("client_origin")
59            .await?
60            .ok_or(ConfigError::NoConfig("client_origin".to_string()))?
61            .to_string();
62
63        let redis_url = system_config("redis_url")
64            .await?
65            .ok_or(ConfigError::NoConfig("redis_url".to_string()))?
66            .to_string();
67
68        let access_token_expires_in = system_config("access_token_expired_in")
69            .await?
70            .ok_or(ConfigError::NoConfig("access_token_expired_in".to_string()))?
71            .to_string();
72
73        let access_token_max_age = system_config("access_token_maxage")
74            .await?
75            .ok_or(ConfigError::NoConfig("access_token_maxage".to_string()))?
76            .to_string();
77
78        let refresh_token_expires_in = system_config("refresh_token_expired_in")
79            .await?
80            .ok_or(ConfigError::NoConfig(
81                "refresh_token_expired_in".to_string(),
82            ))?
83            .to_string();
84
85        let refresh_token_max_age = system_config("refresh_token_maxage")
86            .await?
87            .ok_or(ConfigError::NoConfig("refresh_token_maxage".to_string()))?
88            .to_string();
89
90        let parse_age = |raw: String, field: &str| {
91            raw.parse::<i64>()
92                .map_err(|_| ConfigError::NoConfig(field.to_string()))
93        };
94
95        Ok(Config {
96            site_url,
97            redis_url,
98            client_origin,
99            access_token_expires_in,
100            refresh_token_expires_in,
101            access_token_max_age: parse_age(access_token_max_age, "access_token_maxage")?,
102            refresh_token_max_age: parse_age(refresh_token_max_age, "refresh_token_maxage")?,
103            ssh: SshConnectInfo::from_env(),
104        })
105    }
106}