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: String,
48 pub cookie_domain: String,
50 pub vault_url: 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: String,
114 pub cookie_domain: String,
116 pub vault_url: 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: "ALBAuthSessionCookie".to_string(),
162 cookie_domain: "vault.example.com".to_string(),
163 vault_url: "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, "TestCookie");
182 assert_eq!(vendor.cookie_domain, "example.com");
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!(vendor_config.cookie_name, "ALBAuthSessionCookie");
194 assert_eq!(vendor_config.cookie_domain, "vault.example.com");
195 assert!(vendor_config.cookie_value.is_none());
196 } else {
197 panic!("Expected SsoCookieVendor variant");
198 }
199 }
200
201 #[test]
202 fn cookie_value_some_and_none() {
203 use crate::AcquiredCookie;
204
205 let config_none = SsoCookieVendorConfig {
207 idp_login_url: Some("https://example.com".to_string()),
208 cookie_name: "TestCookie".to_string(),
209 cookie_domain: "example.com".to_string(),
210 vault_url: "https://vault.example.com".to_string(),
211 cookie_value: None,
212 };
213
214 let json_none = serde_json::to_string(&config_none).unwrap();
215 let deserialized_none: SsoCookieVendorConfig = serde_json::from_str(&json_none).unwrap();
216 assert!(deserialized_none.cookie_value.is_none());
217
218 let config_some = SsoCookieVendorConfig {
220 idp_login_url: Some("https://example.com".to_string()),
221 cookie_name: "TestCookie".to_string(),
222 cookie_domain: "example.com".to_string(),
223 vault_url: "https://vault.example.com".to_string(),
224 cookie_value: Some(vec![AcquiredCookie {
225 name: "TestCookie".to_string(),
226 value: "eyJhbGciOiJFUzI1NiIsImtpZCI6Im...".to_string(),
227 }]),
228 };
229
230 let json_some = serde_json::to_string(&config_some).unwrap();
231 let deserialized_some: SsoCookieVendorConfig = serde_json::from_str(&json_some).unwrap();
232 assert_eq!(deserialized_some.cookie_value.as_ref().unwrap().len(), 1);
233 assert_eq!(
234 deserialized_some.cookie_value.as_ref().unwrap()[0].name,
235 "TestCookie"
236 );
237
238 let config_sharded = SsoCookieVendorConfig {
240 idp_login_url: Some("https://example.com".to_string()),
241 cookie_name: "TestCookie".to_string(),
242 cookie_domain: "example.com".to_string(),
243 vault_url: "https://vault.example.com".to_string(),
244 cookie_value: Some(vec![
245 AcquiredCookie {
246 name: "TestCookie-0".to_string(),
247 value: "shard1".to_string(),
248 },
249 AcquiredCookie {
250 name: "TestCookie-1".to_string(),
251 value: "shard2".to_string(),
252 },
253 AcquiredCookie {
254 name: "TestCookie-2".to_string(),
255 value: "shard3".to_string(),
256 },
257 ]),
258 };
259
260 let json_sharded = serde_json::to_string(&config_sharded).unwrap();
261 let deserialized_sharded: SsoCookieVendorConfig =
262 serde_json::from_str(&json_sharded).unwrap();
263 assert_eq!(deserialized_sharded.cookie_value.as_ref().unwrap().len(), 3);
264 assert_eq!(
265 deserialized_sharded.cookie_value.as_ref().unwrap()[0].name,
266 "TestCookie-0"
267 );
268 }
269
270 #[test]
271 fn enum_variants() {
272 let direct = BootstrapConfig::Direct;
273 assert!(matches!(direct, BootstrapConfig::Direct));
274
275 let vendor = BootstrapConfig::SsoCookieVendor(SsoCookieVendorConfig {
276 idp_login_url: Some("https://example.com".to_string()),
277 cookie_name: "Cookie".to_string(),
278 cookie_domain: "example.com".to_string(),
279 vault_url: "https://vault.example.com".to_string(),
280 cookie_value: None,
281 });
282 assert!(matches!(vendor, BootstrapConfig::SsoCookieVendor(_)));
283 }
284
285 #[test]
286 fn debug_output_redacts_cookie_value() {
287 use crate::AcquiredCookie;
288
289 let config_with_cookie = SsoCookieVendorConfig {
291 idp_login_url: Some("https://example.com/login".to_string()),
292 cookie_name: "SessionCookie".to_string(),
293 cookie_domain: "example.com".to_string(),
294 vault_url: "https://vault.example.com".to_string(),
295 cookie_value: Some(vec![AcquiredCookie {
296 name: "SessionCookie".to_string(),
297 value: "super-secret-cookie-value-abc123".to_string(),
298 }]),
299 };
300
301 let debug_output = format!("{:?}", config_with_cookie);
302
303 assert!(debug_output.contains("SsoCookieVendorConfig"));
305 assert!(debug_output.contains("example.com/login"));
306 assert!(debug_output.contains("SessionCookie"));
307 assert!(debug_output.contains("example.com"));
308
309 assert!(!debug_output.contains("super-secret-cookie-value-abc123"));
311 assert!(debug_output.contains("[REDACTED]"));
313 }
314}