Skip to main content

river_core/room_state/
configuration.rs

1use crate::room_state::member::MemberId;
2use crate::room_state::privacy::{PrivacyMode, RoomDisplayMetadata};
3use crate::room_state::ChatRoomParametersV1;
4use crate::util::truncated_base64;
5use crate::ChatRoomStateV1;
6use ed25519_dalek::{Signature, SignatureError, Signer, SigningKey, Verifier, VerifyingKey};
7use freenet_scaffold::util::{fast_hash, FastHash};
8use freenet_scaffold::ComposableState;
9use serde::{Deserialize, Serialize};
10use std::fmt;
11
12#[derive(Serialize, Deserialize, Clone, PartialEq)]
13pub struct AuthorizedConfigurationV1 {
14    pub configuration: Configuration,
15    pub signature: Signature,
16}
17
18impl ComposableState for AuthorizedConfigurationV1 {
19    type ParentState = ChatRoomStateV1;
20    type Summary = u32;
21    type Delta = AuthorizedConfigurationV1;
22    type Parameters = ChatRoomParametersV1;
23
24    fn verify(
25        &self,
26        _parent_state: &Self::ParentState,
27        parameters: &Self::Parameters,
28    ) -> Result<(), String> {
29        self.verify_signature(&parameters.owner)
30            .map_err(|e| format!("Invalid signature: {}", e))
31    }
32
33    fn summarize(
34        &self,
35        _parent_state: &Self::ParentState,
36        _parameters: &Self::Parameters,
37    ) -> Self::Summary {
38        self.configuration.configuration_version
39    }
40
41    fn delta(
42        &self,
43        _parent_state: &Self::ParentState,
44        _parameters: &Self::Parameters,
45        old_version: &Self::Summary,
46    ) -> Option<Self::Delta> {
47        if self.configuration.configuration_version > *old_version {
48            Some(self.clone())
49        } else {
50            None
51        }
52    }
53
54    fn apply_delta(
55        &mut self,
56        _parent_state: &Self::ParentState,
57        parameters: &Self::Parameters,
58        delta: &Option<Self::Delta>,
59    ) -> Result<(), String> {
60        if let Some(delta) = delta {
61            // Verify the delta's signature
62            delta
63                .verify_signature(&parameters.owner)
64                .map_err(|e| format!("Invalid signature: {}", e))?;
65
66            // Check if the new version is greater than the current version
67            if delta.configuration.configuration_version <= self.configuration.configuration_version
68            {
69                return Err(
70                    "New configuration version must be greater than the current version"
71                        .to_string(),
72                );
73            }
74
75            // Verify that the owner_member_id hasn't changed
76            if delta.configuration.owner_member_id != self.configuration.owner_member_id {
77                return Err("Cannot change the owner_member_id".to_string());
78            }
79
80            // Verify that the new configuration is valid
81            if delta.configuration.max_recent_messages == 0
82                || delta.configuration.max_user_bans == 0
83                || delta.configuration.max_message_size == 0
84                || delta.configuration.max_nickname_size == 0
85                || delta.configuration.max_members == 0
86                || delta.configuration.max_room_name == 0
87                || delta.configuration.max_room_description == 0
88            {
89                return Err("Invalid configuration values".to_string());
90            }
91
92            // Validate display metadata declared lengths
93            if delta.configuration.display.name.declared_len() > delta.configuration.max_room_name {
94                return Err(format!(
95                    "Room name declared length {} exceeds max_room_name {}",
96                    delta.configuration.display.name.declared_len(),
97                    delta.configuration.max_room_name
98                ));
99            }
100
101            if let Some(desc) = &delta.configuration.display.description {
102                if desc.declared_len() > delta.configuration.max_room_description {
103                    return Err(format!(
104                        "Room description declared length {} exceeds max_room_description {}",
105                        desc.declared_len(),
106                        delta.configuration.max_room_description
107                    ));
108                }
109            }
110
111            // In private mode, ensure display metadata is encrypted
112            if delta.configuration.privacy_mode == PrivacyMode::Private
113                && delta.configuration.display.name.is_public()
114            {
115                return Err("Private room must have encrypted display metadata".to_string());
116            }
117
118            // If all checks pass, apply the delta
119            self.configuration = delta.configuration.clone();
120            self.signature = delta.signature;
121        }
122
123        Ok(())
124    }
125}
126
127impl AuthorizedConfigurationV1 {
128    pub fn new(configuration: Configuration, owner_signing_key: &SigningKey) -> Self {
129        let mut serialized_config = Vec::new();
130        ciborium::ser::into_writer(&configuration, &mut serialized_config)
131            .expect("Serialization should not fail");
132        let signature = owner_signing_key.sign(&serialized_config);
133
134        Self {
135            configuration,
136            signature,
137        }
138    }
139
140    /// Create an AuthorizedConfigurationV1 with a pre-computed signature.
141    /// Use this when signing is done externally (e.g., via delegate).
142    pub fn with_signature(configuration: Configuration, signature: Signature) -> Self {
143        Self {
144            configuration,
145            signature,
146        }
147    }
148
149    pub fn verify_signature(
150        &self,
151        owner_verifying_key: &VerifyingKey,
152    ) -> Result<(), SignatureError> {
153        let mut serialized_config = Vec::new();
154        ciborium::ser::into_writer(&self.configuration, &mut serialized_config)
155            .expect("Serialization should not fail");
156        owner_verifying_key.verify(&serialized_config, &self.signature)
157    }
158
159    pub fn id(&self) -> FastHash {
160        fast_hash(&self.signature.to_bytes())
161    }
162}
163
164impl Default for AuthorizedConfigurationV1 {
165    fn default() -> Self {
166        let default_config = Configuration::default();
167        let default_key = SigningKey::from_bytes(&[0; 32]);
168        Self::new(default_config, &default_key)
169    }
170}
171
172impl Default for Configuration {
173    fn default() -> Self {
174        Configuration {
175            owner_member_id: MemberId(FastHash(0)), // Default value, should be overwritten
176            configuration_version: 1,
177            privacy_mode: PrivacyMode::default(),
178            display: RoomDisplayMetadata::default(),
179            max_recent_messages: 100,
180            max_user_bans: 10,
181            max_message_size: 1000,
182            max_nickname_size: 50,
183            max_members: 200,
184            max_room_name: 100,
185            max_room_description: 500,
186        }
187    }
188}
189
190impl fmt::Debug for AuthorizedConfigurationV1 {
191    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192        f.debug_struct("AuthorizedConfiguration")
193            .field("configuration", &self.configuration)
194            .field(
195                "signature",
196                &format_args!("{}", truncated_base64(self.signature.to_bytes())),
197            )
198            .finish()
199    }
200}
201
202#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
203pub struct Configuration {
204    pub owner_member_id: MemberId,
205    pub configuration_version: u32,
206    pub privacy_mode: PrivacyMode,
207    pub display: RoomDisplayMetadata,
208    pub max_recent_messages: usize,
209    pub max_user_bans: usize,
210    pub max_message_size: usize,
211    pub max_nickname_size: usize,
212    pub max_members: usize,
213    pub max_room_name: usize,
214    pub max_room_description: usize,
215}
216
217#[cfg(test)]
218mod tests {
219    use super::*;
220    use rand::rngs::OsRng;
221
222    #[test]
223    fn test_verify() {
224        let owner_signing_key = SigningKey::generate(&mut OsRng);
225        let owner_verifying_key = VerifyingKey::from(&owner_signing_key);
226        let configuration = Configuration::default();
227        let authorized_configuration =
228            AuthorizedConfigurationV1::new(configuration.clone(), &owner_signing_key);
229
230        assert!(authorized_configuration
231            .verify_signature(&owner_verifying_key)
232            .is_ok());
233
234        let parent_state = ChatRoomStateV1 {
235            configuration: authorized_configuration.clone(),
236            ..ChatRoomStateV1::default()
237        };
238        let parameters = ChatRoomParametersV1 {
239            owner: owner_verifying_key,
240        };
241
242        assert!(authorized_configuration
243            .verify(&parent_state, &parameters)
244            .is_ok());
245    }
246
247    #[test]
248    fn test_verify_fail() {
249        let owner_signing_key = SigningKey::generate(&mut OsRng);
250        let configuration = Configuration::default();
251        let authorized_configuration =
252            AuthorizedConfigurationV1::new(configuration.clone(), &owner_signing_key);
253
254        let wrong_owner_signing_key = SigningKey::generate(&mut OsRng);
255        let wrong_owner_verifying_key = VerifyingKey::from(&wrong_owner_signing_key);
256
257        assert!(authorized_configuration
258            .verify_signature(&wrong_owner_verifying_key)
259            .is_err());
260
261        let parent_state = ChatRoomStateV1 {
262            configuration: authorized_configuration.clone(),
263            ..ChatRoomStateV1::default()
264        };
265        let parameters = ChatRoomParametersV1 {
266            owner: wrong_owner_verifying_key,
267        };
268
269        assert!(authorized_configuration
270            .verify(&parent_state, &parameters)
271            .is_err());
272    }
273
274    #[test]
275    fn test_summarize() {
276        let owner_signing_key = SigningKey::generate(&mut OsRng);
277        let owner_verifying_key = VerifyingKey::from(&owner_signing_key);
278        let configuration = Configuration::default();
279        let authorized_configuration =
280            AuthorizedConfigurationV1::new(configuration.clone(), &owner_signing_key);
281
282        let parent_state = ChatRoomStateV1 {
283            configuration: authorized_configuration.clone(),
284            ..Default::default()
285        };
286        let parameters = ChatRoomParametersV1 {
287            owner: owner_verifying_key,
288        };
289
290        assert_eq!(
291            authorized_configuration.summarize(&parent_state, &parameters),
292            configuration.configuration_version
293        );
294    }
295
296    #[test]
297    fn test_delta_new_version() {
298        let owner_signing_key = SigningKey::generate(&mut OsRng);
299        let owner_verifying_key = VerifyingKey::from(&owner_signing_key);
300        let configuration = Configuration::default();
301        let authorized_configuration =
302            AuthorizedConfigurationV1::new(configuration.clone(), &owner_signing_key);
303
304        let parent_state = ChatRoomStateV1 {
305            configuration: authorized_configuration.clone(),
306            ..Default::default()
307        };
308        let parameters = ChatRoomParametersV1 {
309            owner: owner_verifying_key,
310        };
311
312        let new_configuration = Configuration {
313            configuration_version: 2,
314            ..configuration.clone()
315        };
316        let new_authorized_configuration =
317            AuthorizedConfigurationV1::new(new_configuration.clone(), &owner_signing_key);
318
319        assert_eq!(
320            new_authorized_configuration.delta(&parent_state, &parameters, &1),
321            Some(new_authorized_configuration)
322        );
323    }
324
325    #[test]
326    fn test_delta_older_version() {
327        let owner_signing_key = SigningKey::generate(&mut OsRng);
328        let owner_verifying_key = VerifyingKey::from(&owner_signing_key);
329
330        // Create an older configuration (version 1)
331        let old_configuration = Configuration {
332            configuration_version: 1,
333            ..Configuration::default()
334        };
335        let old_authorized_configuration =
336            AuthorizedConfigurationV1::new(old_configuration.clone(), &owner_signing_key);
337
338        let parent_state = ChatRoomStateV1 {
339            configuration: old_authorized_configuration.clone(),
340            ..Default::default()
341        };
342        let parameters = ChatRoomParametersV1 {
343            owner: owner_verifying_key,
344        };
345
346        // Test against a newer version (2)
347        // The delta should return None since our configuration is older
348        assert_eq!(
349            old_authorized_configuration.delta(&parent_state, &parameters, &2),
350            None
351        );
352    }
353
354    #[test]
355    fn test_apply_delta_should_apply() {
356        let owner_signing_key = SigningKey::generate(&mut OsRng);
357        let owner_verifying_key = VerifyingKey::from(&owner_signing_key);
358        let configuration = Configuration::default();
359        let mut authorized_configuration =
360            AuthorizedConfigurationV1::new(configuration.clone(), &owner_signing_key);
361
362        let parent_state = ChatRoomStateV1 {
363            configuration: authorized_configuration.clone(),
364            ..Default::default()
365        };
366        let parameters = ChatRoomParametersV1 {
367            owner: owner_verifying_key,
368        };
369
370        let new_configuration = Configuration {
371            configuration_version: 2,
372            ..configuration.clone()
373        };
374        let new_authorized_configuration =
375            AuthorizedConfigurationV1::new(new_configuration.clone(), &owner_signing_key);
376
377        authorized_configuration
378            .apply_delta(
379                &parent_state,
380                &parameters,
381                &Some(new_authorized_configuration.clone()),
382            )
383            .unwrap();
384
385        assert_eq!(authorized_configuration, new_authorized_configuration);
386    }
387
388    #[test]
389    fn test_apply_delta_old_version() {
390        let owner_signing_key = SigningKey::generate(&mut OsRng);
391        let owner_verifying_key = VerifyingKey::from(&owner_signing_key);
392        let configuration = Configuration::default();
393        let mut authorized_configuration =
394            AuthorizedConfigurationV1::new(configuration.clone(), &owner_signing_key);
395
396        let orig_authorized_configuration = authorized_configuration.clone();
397
398        let parent_state = ChatRoomStateV1 {
399            configuration: authorized_configuration.clone(),
400            ..Default::default()
401        };
402        let parameters = ChatRoomParametersV1 {
403            owner: owner_verifying_key,
404        };
405
406        let new_configuration = Configuration {
407            configuration_version: 0,
408            ..configuration.clone()
409        };
410        let new_authorized_configuration =
411            AuthorizedConfigurationV1::new(new_configuration.clone(), &owner_signing_key);
412
413        let result = authorized_configuration.apply_delta(
414            &parent_state,
415            &parameters,
416            &Some(new_authorized_configuration),
417        );
418
419        assert!(result.is_err());
420        assert_eq!(
421            result.unwrap_err(),
422            "New configuration version must be greater than the current version"
423        );
424        assert_eq!(authorized_configuration, orig_authorized_configuration);
425    }
426
427    #[test]
428    fn test_apply_delta_change_owner() {
429        let owner_signing_key = SigningKey::generate(&mut OsRng);
430        let owner_verifying_key = VerifyingKey::from(&owner_signing_key);
431        let configuration = Configuration {
432            owner_member_id: MemberId(FastHash(1)),
433            ..Configuration::default()
434        };
435        let mut authorized_configuration =
436            AuthorizedConfigurationV1::new(configuration.clone(), &owner_signing_key);
437
438        let parent_state = ChatRoomStateV1 {
439            configuration: authorized_configuration.clone(),
440            ..Default::default()
441        };
442        let parameters = ChatRoomParametersV1 {
443            owner: owner_verifying_key,
444        };
445
446        let mut new_configuration = configuration.clone();
447        new_configuration.configuration_version += 1;
448        new_configuration.owner_member_id = MemberId(FastHash(2));
449        let new_authorized_configuration =
450            AuthorizedConfigurationV1::new(new_configuration, &owner_signing_key);
451
452        let result = authorized_configuration.apply_delta(
453            &parent_state,
454            &parameters,
455            &Some(new_authorized_configuration),
456        );
457
458        assert!(result.is_err());
459        assert_eq!(result.unwrap_err(), "Cannot change the owner_member_id");
460    }
461
462    #[test]
463    fn test_apply_delta_invalid_values() {
464        let owner_signing_key = SigningKey::generate(&mut OsRng);
465        let owner_verifying_key = VerifyingKey::from(&owner_signing_key);
466        let configuration = Configuration::default();
467        let mut authorized_configuration =
468            AuthorizedConfigurationV1::new(configuration.clone(), &owner_signing_key);
469
470        let parent_state = ChatRoomStateV1 {
471            configuration: authorized_configuration.clone(),
472            ..Default::default()
473        };
474        let parameters = ChatRoomParametersV1 {
475            owner: owner_verifying_key,
476        };
477
478        let mut new_configuration = configuration.clone();
479        new_configuration.configuration_version += 1;
480        new_configuration.max_recent_messages = 0;
481        let new_authorized_configuration =
482            AuthorizedConfigurationV1::new(new_configuration, &owner_signing_key);
483
484        let result = authorized_configuration.apply_delta(
485            &parent_state,
486            &parameters,
487            &Some(new_authorized_configuration),
488        );
489
490        assert!(result.is_err());
491        assert_eq!(result.unwrap_err(), "Invalid configuration values");
492    }
493}