1use serde::{Deserialize, Serialize};
2use std::path::PathBuf;
3
4#[derive(Debug, Clone, Deserialize, Serialize)]
5pub struct ServerConfig {
6 #[serde(default = "default_host")]
7 pub host: String,
8 pub port: u16,
11}
12
13#[derive(Debug, Clone, Deserialize, Serialize)]
14pub struct LogConfig {
15 #[serde(default = "default_log_level")]
16 pub level: String,
17 #[serde(default)]
18 pub format: LogFormat,
19}
20
21#[derive(Debug, Clone, Deserialize, Serialize)]
22pub struct StoreConfig {
23 pub data_dir: PathBuf,
26}
27
28#[derive(Clone, Deserialize, Serialize)]
29pub struct AuthConfig {
30 #[serde(default = "default_access_token_expiry")]
31 pub access_token_expiry: u64,
32 #[serde(default = "default_refresh_token_expiry")]
33 pub refresh_token_expiry: u64,
34 #[serde(default = "default_challenge_ttl")]
35 pub challenge_ttl: u64,
36 #[serde(default = "default_session_cleanup_interval")]
37 pub session_cleanup_interval: u64,
38 pub jwt_signing_key: Option<String>,
40 #[serde(default)]
44 pub step_up: crate::auth::step_up::StepUpPolicy,
45}
46
47impl std::fmt::Debug for AuthConfig {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 f.debug_struct("AuthConfig")
56 .field("access_token_expiry", &self.access_token_expiry)
57 .field("refresh_token_expiry", &self.refresh_token_expiry)
58 .field("challenge_ttl", &self.challenge_ttl)
59 .field("session_cleanup_interval", &self.session_cleanup_interval)
60 .field(
61 "jwt_signing_key",
62 &self.jwt_signing_key.as_ref().map(|_| "<redacted>"),
63 )
64 .field("step_up", &self.step_up)
65 .finish()
66 }
67}
68
69#[derive(Debug, Clone, Deserialize, Serialize)]
70pub struct MessagingConfig {
71 #[serde(default)]
74 pub mediator_url: String,
75 pub mediator_did: String,
76 #[serde(default)]
80 pub mediator_host: Option<String>,
81 #[serde(default)]
86 pub setup_acl: bool,
87 #[serde(default)]
102 pub drain_inbox_on_start: bool,
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct AuditConfig {
107 #[serde(default = "default_audit_retention_days")]
109 pub retention_days: u32,
110}
111
112fn default_audit_retention_days() -> u32 {
113 28
114}
115
116impl Default for AuditConfig {
117 fn default() -> Self {
118 Self {
119 retention_days: default_audit_retention_days(),
120 }
121 }
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct VaultConfig {
128 #[serde(default = "default_vault_grace_days")]
134 pub grace_days: u32,
135}
136
137fn default_vault_grace_days() -> u32 {
138 30
139}
140
141impl Default for VaultConfig {
142 fn default() -> Self {
143 Self {
144 grace_days: default_vault_grace_days(),
145 }
146 }
147}
148
149#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq)]
150#[serde(rename_all = "lowercase")]
151pub enum LogFormat {
152 #[default]
153 Text,
154 Json,
155}
156
157fn default_host() -> String {
158 "0.0.0.0".to_string()
159}
160
161fn default_log_level() -> String {
162 "info".to_string()
163}
164
165fn default_access_token_expiry() -> u64 {
166 900
167}
168
169fn default_refresh_token_expiry() -> u64 {
170 86400
171}
172
173fn default_challenge_ttl() -> u64 {
174 300
175}
176
177fn default_session_cleanup_interval() -> u64 {
178 600
179}
180
181impl Default for AuthConfig {
182 fn default() -> Self {
183 Self {
184 access_token_expiry: default_access_token_expiry(),
185 refresh_token_expiry: default_refresh_token_expiry(),
186 challenge_ttl: default_challenge_ttl(),
187 session_cleanup_interval: default_session_cleanup_interval(),
188 jwt_signing_key: None,
189 step_up: crate::auth::step_up::StepUpPolicy::default(),
190 }
191 }
192}
193
194impl Default for LogConfig {
195 fn default() -> Self {
196 Self {
197 level: default_log_level(),
198 format: LogFormat::default(),
199 }
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206
207 #[test]
212 fn auth_config_debug_redacts_jwt_signing_key() {
213 let cfg = AuthConfig {
214 access_token_expiry: 900,
215 refresh_token_expiry: 86400,
216 challenge_ttl: 300,
217 session_cleanup_interval: 600,
218 jwt_signing_key: Some("SUPER_SECRET_KEY_MATERIAL_MUST_NOT_LEAK".into()),
219 step_up: Default::default(),
220 };
221 let dbg = format!("{cfg:?}");
222 assert!(
223 !dbg.contains("SUPER_SECRET_KEY_MATERIAL"),
224 "AuthConfig Debug leaked jwt_signing_key contents: {dbg}"
225 );
226 assert!(
227 dbg.contains("<redacted>"),
228 "expected redaction marker in Debug, got: {dbg}"
229 );
230 assert!(
232 dbg.contains("900"),
233 "access_token_expiry must still be visible: {dbg}"
234 );
235 }
236
237 #[test]
238 fn auth_config_debug_none_signing_key_renders_none() {
239 let cfg = AuthConfig::default();
240 let dbg = format!("{cfg:?}");
241 assert!(dbg.contains("jwt_signing_key: None"), "got: {dbg}");
243 }
244
245 #[test]
251 fn auth_config_serialize_still_carries_jwt_signing_key() {
252 let cfg = AuthConfig {
253 access_token_expiry: 900,
254 refresh_token_expiry: 86400,
255 challenge_ttl: 300,
256 session_cleanup_interval: 600,
257 jwt_signing_key: Some("key-material".into()),
258 step_up: Default::default(),
259 };
260 let json = serde_json::to_string(&cfg).expect("serialize");
261 assert!(
262 json.contains("key-material"),
263 "Serialize must not redact — config persistence relies on round-trip: {json}"
264 );
265 }
266}