bitwarden_server_communication_config/
config.rs1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize)]
5#[cfg_attr(
6 feature = "wasm",
7 derive(tsify::Tsify),
8 tsify(into_wasm_abi, from_wasm_abi)
9)]
10#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
11pub struct ServerCommunicationConfig {
12 pub bootstrap: BootstrapConfig,
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18#[cfg_attr(
19 feature = "wasm",
20 derive(tsify::Tsify),
21 tsify(into_wasm_abi, from_wasm_abi)
22)]
23#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
24#[serde(tag = "type", rename_all = "camelCase")]
25pub enum BootstrapConfig {
26 Direct,
28 SsoCookieVendor(SsoCookieVendorConfig),
30}
31
32#[derive(Clone, Serialize, Deserialize)]
36#[serde(rename_all = "camelCase")]
37#[cfg_attr(
38 feature = "wasm",
39 derive(tsify::Tsify),
40 tsify(into_wasm_abi, from_wasm_abi)
41)]
42#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
43pub struct SsoCookieVendorConfig {
44 pub idp_login_url: Option<String>,
46 pub cookie_name: Option<String>,
48 pub cookie_domain: Option<String>,
50 pub vault_url: Option<String>,
55 pub cookie_value: Option<Vec<crate::AcquiredCookie>>,
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
71#[cfg_attr(
72 feature = "wasm",
73 derive(tsify::Tsify),
74 tsify(into_wasm_abi, from_wasm_abi)
75)]
76#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
77pub struct SetCommunicationTypeRequest {
78 pub bootstrap: BootstrapConfigRequest,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
84#[cfg_attr(
85 feature = "wasm",
86 derive(tsify::Tsify),
87 tsify(into_wasm_abi, from_wasm_abi)
88)]
89#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
90#[serde(tag = "type", rename_all = "camelCase")]
91pub enum BootstrapConfigRequest {
92 Direct,
94 SsoCookieVendor(SsoCookieVendorConfigRequest),
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize)]
102#[serde(rename_all = "camelCase")]
103#[cfg_attr(
104 feature = "wasm",
105 derive(tsify::Tsify),
106 tsify(into_wasm_abi, from_wasm_abi)
107)]
108#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
109pub struct SsoCookieVendorConfigRequest {
110 pub idp_login_url: Option<String>,
112 pub cookie_name: Option<String>,
114 pub cookie_domain: Option<String>,
116 pub vault_url: Option<String>,
121}
122
123impl std::fmt::Debug for SsoCookieVendorConfig {
125 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126 f.debug_struct("SsoCookieVendorConfig")
127 .field("idp_login_url", &self.idp_login_url)
128 .field("cookie_name", &self.cookie_name)
129 .field("cookie_domain", &self.cookie_domain)
130 .field("vault_url", &self.vault_url)
131 .field(
132 "cookie_value",
133 &self.cookie_value.as_ref().map(|_| "[REDACTED]"),
134 )
135 .finish()
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
144 fn direct_bootstrap_serialization() {
145 let config = ServerCommunicationConfig {
146 bootstrap: BootstrapConfig::Direct,
147 };
148
149 let json = serde_json::to_string(&config).unwrap();
150 assert!(json.contains("\"type\":\"direct\""));
151
152 let deserialized: ServerCommunicationConfig = serde_json::from_str(&json).unwrap();
153 assert!(matches!(deserialized.bootstrap, BootstrapConfig::Direct));
154 }
155
156 #[test]
157 fn sso_cookie_vendor_serialization() {
158 let config = ServerCommunicationConfig {
159 bootstrap: BootstrapConfig::SsoCookieVendor(SsoCookieVendorConfig {
160 idp_login_url: Some("https://timeloop-auth.acme.com/login".to_string()),
161 cookie_name: Some("ALBAuthSessionCookie".to_string()),
162 cookie_domain: Some("vault.example.com".to_string()),
163 vault_url: Some("https://vault.example.com".to_string()),
164 cookie_value: None,
165 }),
166 };
167
168 let json = serde_json::to_string(&config).unwrap();
169 assert!(json.contains("\"type\":\"ssoCookieVendor\""));
170 assert!(json.contains("timeloop-auth.acme.com"));
171 assert!(json.contains("ALBAuthSessionCookie"));
172
173 let server_json = r#"{"bootstrap":{"type":"ssoCookieVendor","idpLoginUrl":"https://idp.example.com/login","cookieName":"TestCookie","cookieDomain":"example.com","vaultUrl":"https://vault.example.com"}}"#;
175 let parsed = serde_json::from_str::<ServerCommunicationConfig>(server_json).unwrap();
176 if let BootstrapConfig::SsoCookieVendor(vendor) = parsed.bootstrap {
177 assert_eq!(
178 vendor.idp_login_url,
179 Some("https://idp.example.com/login".to_string())
180 );
181 assert_eq!(vendor.cookie_name, Some("TestCookie".to_string()));
182 assert_eq!(vendor.cookie_domain, Some("example.com".to_string()));
183 } else {
184 panic!("Expected SsoCookieVendor variant");
185 }
186
187 let deserialized: ServerCommunicationConfig = serde_json::from_str(&json).unwrap();
188 if let BootstrapConfig::SsoCookieVendor(vendor_config) = deserialized.bootstrap {
189 assert_eq!(
190 vendor_config.idp_login_url,
191 Some("https://timeloop-auth.acme.com/login".to_string())
192 );
193 assert_eq!(
194 vendor_config.cookie_name,
195 Some("ALBAuthSessionCookie".to_string())
196 );
197 assert_eq!(
198 vendor_config.cookie_domain,
199 Some("vault.example.com".to_string())
200 );
201 assert!(vendor_config.cookie_value.is_none());
202 } else {
203 panic!("Expected SsoCookieVendor variant");
204 }
205 }
206
207 #[test]
208 fn cookie_value_some_and_none() {
209 use crate::AcquiredCookie;
210
211 let config_none = SsoCookieVendorConfig {
213 idp_login_url: Some("https://example.com".to_string()),
214 cookie_name: Some("TestCookie".to_string()),
215 cookie_domain: Some("example.com".to_string()),
216 vault_url: Some("https://vault.example.com".to_string()),
217 cookie_value: None,
218 };
219
220 let json_none = serde_json::to_string(&config_none).unwrap();
221 let deserialized_none: SsoCookieVendorConfig = serde_json::from_str(&json_none).unwrap();
222 assert!(deserialized_none.cookie_value.is_none());
223
224 let config_some = SsoCookieVendorConfig {
226 idp_login_url: Some("https://example.com".to_string()),
227 cookie_name: Some("TestCookie".to_string()),
228 cookie_domain: Some("example.com".to_string()),
229 vault_url: Some("https://vault.example.com".to_string()),
230 cookie_value: Some(vec![AcquiredCookie {
231 name: "TestCookie".to_string(),
232 value: "eyJhbGciOiJFUzI1NiIsImtpZCI6Im...".to_string(),
233 }]),
234 };
235
236 let json_some = serde_json::to_string(&config_some).unwrap();
237 let deserialized_some: SsoCookieVendorConfig = serde_json::from_str(&json_some).unwrap();
238 assert_eq!(deserialized_some.cookie_value.as_ref().unwrap().len(), 1);
239 assert_eq!(
240 deserialized_some.cookie_value.as_ref().unwrap()[0].name,
241 "TestCookie"
242 );
243
244 let config_sharded = SsoCookieVendorConfig {
246 idp_login_url: Some("https://example.com".to_string()),
247 cookie_name: Some("TestCookie".to_string()),
248 cookie_domain: Some("example.com".to_string()),
249 vault_url: Some("https://vault.example.com".to_string()),
250 cookie_value: Some(vec![
251 AcquiredCookie {
252 name: "TestCookie-0".to_string(),
253 value: "shard1".to_string(),
254 },
255 AcquiredCookie {
256 name: "TestCookie-1".to_string(),
257 value: "shard2".to_string(),
258 },
259 AcquiredCookie {
260 name: "TestCookie-2".to_string(),
261 value: "shard3".to_string(),
262 },
263 ]),
264 };
265
266 let json_sharded = serde_json::to_string(&config_sharded).unwrap();
267 let deserialized_sharded: SsoCookieVendorConfig =
268 serde_json::from_str(&json_sharded).unwrap();
269 assert_eq!(deserialized_sharded.cookie_value.as_ref().unwrap().len(), 3);
270 assert_eq!(
271 deserialized_sharded.cookie_value.as_ref().unwrap()[0].name,
272 "TestCookie-0"
273 );
274 }
275
276 #[test]
277 fn enum_variants() {
278 let direct = BootstrapConfig::Direct;
279 assert!(matches!(direct, BootstrapConfig::Direct));
280
281 let vendor = BootstrapConfig::SsoCookieVendor(SsoCookieVendorConfig {
282 idp_login_url: Some("https://example.com".to_string()),
283 cookie_name: Some("Cookie".to_string()),
284 cookie_domain: Some("example.com".to_string()),
285 vault_url: Some("https://vault.example.com".to_string()),
286 cookie_value: None,
287 });
288 assert!(matches!(vendor, BootstrapConfig::SsoCookieVendor(_)));
289 }
290
291 #[test]
292 fn debug_output_redacts_cookie_value() {
293 use crate::AcquiredCookie;
294
295 let config_with_cookie = SsoCookieVendorConfig {
297 idp_login_url: Some("https://example.com/login".to_string()),
298 cookie_name: Some("SessionCookie".to_string()),
299 cookie_domain: Some("example.com".to_string()),
300 vault_url: Some("https://vault.example.com".to_string()),
301 cookie_value: Some(vec![AcquiredCookie {
302 name: "SessionCookie".to_string(),
303 value: "super-secret-cookie-value-abc123".to_string(),
304 }]),
305 };
306
307 let debug_output = format!("{:?}", config_with_cookie);
308
309 assert!(debug_output.contains("SsoCookieVendorConfig"));
311 assert!(debug_output.contains("example.com/login"));
312 assert!(debug_output.contains("SessionCookie"));
313 assert!(debug_output.contains("example.com"));
314
315 assert!(!debug_output.contains("super-secret-cookie-value-abc123"));
317 assert!(debug_output.contains("[REDACTED]"));
319 }
320}