Skip to main content

haste_server/
config.rs

1use derivative::Derivative;
2use serde::{Deserialize, Serialize};
3use std::path::PathBuf;
4
5#[derive(Debug, Clone, Deserialize, Serialize)]
6#[serde(default)]
7pub struct ServerConfig {
8    pub allow_artifact_mutations: bool,
9    /// Used for JWT signing/verification.
10    pub certification_dir: PathBuf,
11    /// Main root where the FHIR server is hosted.
12    pub api_uri: String,
13    /// Where to redirect for the hardcoded admin app.
14    pub admin_app_redirect_uri: String,
15
16    pub fhir: FHIRConfig,
17    pub repo: RepoConfig,
18    pub search: SearchConfig,
19    pub email: Option<EmailConfig>,
20    pub rate_limits: RateLimitsConfig,
21    pub max_request_body_size: usize,
22    pub monitoring: MonitoringConfig,
23    pub security: SecurityConfig,
24}
25
26#[derive(Debug, Clone, Deserialize, Serialize)]
27#[serde(default)]
28pub struct SecurityConfig {
29    pub publicize_fhir_metadata: bool,
30    pub mfa: MFAConfig,
31    pub encryption: SecretProviderConfig,
32    pub aes_key: Option<String>,
33    pub certification_key: Option<String>,
34}
35
36#[derive(Debug, Clone, Deserialize, Serialize)]
37#[serde(default)]
38pub struct MFAConfig {
39    pub max_credentials_per_user: usize,
40}
41
42#[derive(Debug, Clone, Deserialize, Serialize)]
43#[serde(default)]
44pub struct MonitoringConfig {
45    pub audit_enabled: bool,
46    pub ip_source: IpSource,
47}
48
49#[derive(Debug, Clone, Deserialize, Serialize)]
50#[serde(tag = "type", rename_all = "snake_case")]
51pub enum SecretProviderConfig {
52    Environment { prefix: Option<String> },
53    GCP { project_id: String },
54    AWS { region: String },
55}
56
57#[derive(Debug, Clone, Deserialize, Serialize)]
58#[serde(default)]
59pub struct FHIRConfig {
60    /// Max delete limit for type-delete and system-delete operations.
61    pub delete_limit: usize,
62}
63
64// Repo backend where the FHIR server stores its data/resources.
65#[derive(Debug, Clone, Deserialize, Serialize)]
66#[serde(tag = "backend", rename_all = "snake_case")]
67pub enum RepoConfig {
68    Postgres(PostgresConfig),
69}
70
71#[derive(Derivative, Clone, Deserialize, Serialize)]
72#[derivative(Debug)]
73pub struct PostgresConfig {
74    #[derivative(Debug = "ignore")]
75    pub database_url: String,
76    pub max_connections: u32,
77}
78
79// Search backend where the FHIR server stores its search indices.
80#[derive(Debug, Clone, Deserialize, Serialize)]
81#[serde(tag = "backend", rename_all = "snake_case")]
82pub enum SearchConfig {
83    Elasticsearch(ElasticsearchConfig),
84}
85
86#[derive(Derivative, Clone, Deserialize, Serialize)]
87#[derivative(Debug)]
88pub struct ElasticsearchConfig {
89    pub url: String,
90    #[derivative(Debug = "ignore")]
91    pub username: String,
92    #[derivative(Debug = "ignore")]
93    pub password: String,
94}
95
96#[derive(Derivative, Clone, Deserialize, Serialize)]
97#[derivative(Debug)]
98#[serde(tag = "backend", rename_all = "snake_case")]
99pub enum EmailConfig {
100    SendGrid {
101        #[derivative(Debug = "ignore")]
102        api_key: String,
103        #[derivative(Debug = "ignore")]
104        from_address: String,
105    },
106}
107
108#[derive(Debug, Clone, Deserialize, Serialize)]
109#[serde(default)]
110pub struct RateLimitsConfig {
111    pub rate_limit_subscription_tiers: Option<[usize; 4]>,
112    pub rate_limit_window_seconds: u64,
113    pub rate_limit_operation_points: u32,
114}
115
116#[derive(Debug, Clone, Deserialize, Serialize, Default)]
117#[serde(rename_all = "snake_case")]
118pub enum IpSource {
119    #[default]
120    ConnectInfo,
121    CfConnectingIp,
122    XRealIp,
123}
124
125impl Default for FHIRConfig {
126    fn default() -> Self {
127        Self { delete_limit: 100 }
128    }
129}
130
131impl Default for ServerConfig {
132    fn default() -> Self {
133        Self {
134            allow_artifact_mutations: false,
135            certification_dir: PathBuf::from("certifications"),
136            api_uri: "http://localhost:3000".into(),
137            admin_app_redirect_uri: "http://*.localhost:3001".into(),
138            fhir: FHIRConfig::default(),
139            repo: RepoConfig::default(),
140            search: SearchConfig::default(),
141            email: None,
142            max_request_body_size: 4 * 1024 * 1024,
143            rate_limits: RateLimitsConfig::default(),
144            monitoring: MonitoringConfig::default(),
145            security: SecurityConfig::default(),
146        }
147    }
148}
149impl Default for RepoConfig {
150    fn default() -> Self {
151        RepoConfig::Postgres(PostgresConfig::default())
152    }
153}
154impl Default for PostgresConfig {
155    fn default() -> Self {
156        Self {
157            database_url: "postgresql://postgres:postgres@localhost:5432/haste_health".into(),
158            max_connections: 10,
159        }
160    }
161}
162impl Default for SearchConfig {
163    fn default() -> Self {
164        SearchConfig::Elasticsearch(ElasticsearchConfig::default())
165    }
166}
167impl Default for ElasticsearchConfig {
168    fn default() -> Self {
169        Self {
170            url: "http://localhost:9200".into(),
171            username: "elastic".into(),
172            password: "elastic".into(),
173        }
174    }
175}
176
177impl Default for RateLimitsConfig {
178    fn default() -> Self {
179        Self {
180            rate_limit_subscription_tiers: None,
181            rate_limit_window_seconds: 60 * 60 * 24, // 1 day in seconds
182            rate_limit_operation_points: 100,
183        }
184    }
185}
186
187impl Default for MonitoringConfig {
188    fn default() -> Self {
189        Self {
190            audit_enabled: false,
191            ip_source: IpSource::default(),
192        }
193    }
194}
195
196impl Default for SecurityConfig {
197    fn default() -> Self {
198        Self {
199            publicize_fhir_metadata: true,
200            mfa: MFAConfig::default(),
201            encryption: SecretProviderConfig::default(),
202            aes_key: None,
203            certification_key: None,
204        }
205    }
206}
207
208impl Default for MFAConfig {
209    fn default() -> Self {
210        Self {
211            max_credentials_per_user: 1,
212        }
213    }
214}
215
216impl Default for SecretProviderConfig {
217    fn default() -> Self {
218        Self::Environment {
219            prefix: Some("HASTE_SECRET_".to_string()),
220        }
221    }
222}