syncweb_core/net/
membership_doc.rs1use ed25519_dalek::SigningKey;
2use iroh_docs::{AuthorId, api::Doc};
3use serde::{Deserialize, Serialize};
4
5use crate::{
6 Result, SyncwebError,
7 constants::{MEMBER_LIST_SIGNATURE_CONTEXT, NETWORK_MEMBERS_KEY},
8 node::{blob_store::BlobStore, docs_engine::DocsEngine},
9 parsing::current_unix_secs,
10};
11
12use super::network::Network;
13
14#[derive(Clone, Debug, Serialize, Deserialize)]
16#[non_exhaustive]
17pub struct SignedMemberList {
18 pub network_id: String,
19 pub owner: String,
20 pub sequence: u64,
21 pub members: Vec<MemberEntry>,
22 pub updated_at: u64,
23 pub signature: String,
24}
25
26#[derive(Clone, Debug, Serialize, Deserialize)]
28#[non_exhaustive]
29pub struct MemberEntry {
30 pub key: String,
31 pub joined_at: u64,
32 pub role: MemberRole,
33}
34
35#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
37#[non_exhaustive]
38pub enum MemberRole {
39 Admin,
40 Member,
41}
42
43impl SignedMemberList {
44 #[must_use]
46 pub fn new(network_id: &str, owner: &str) -> Self {
47 Self {
48 network_id: network_id.to_owned(),
49 owner: owner.to_owned(),
50 sequence: 0,
51 members: Vec::new(),
52 updated_at: current_unix_secs(),
53 signature: String::new(),
54 }
55 }
56
57 #[must_use]
63 pub fn from_network(network: &Network) -> Self {
64 let owner = hex::encode(network.owner.as_bytes());
65 let now = current_unix_secs();
66 let mut members = network
67 .members
68 .iter()
69 .map(|key| MemberEntry {
70 key: key.to_string(),
71 joined_at: now,
72 role: if *key == network.owner {
73 MemberRole::Admin
74 } else {
75 MemberRole::Member
76 },
77 })
78 .collect::<Vec<_>>();
79 members.sort_by(|left, right| left.key.cmp(&right.key));
80 Self {
81 network_id: network.id.to_string(),
82 owner,
83 sequence: 0,
84 members,
85 updated_at: now,
86 signature: String::new(),
87 }
88 }
89
90 pub fn sign(&mut self, signing_key: &SigningKey) -> Result<()> {
96 let owner_hex = crate::signature::public_key_hex(signing_key);
97 if self.owner != owner_hex {
98 return Err(SyncwebError::InvalidSignature(
99 "signing key does not match owner field".to_owned(),
100 ));
101 }
102 let message = self.serialize_unsigned()?;
103 let mut signed_bytes = Vec::new();
104 signed_bytes.extend_from_slice(MEMBER_LIST_SIGNATURE_CONTEXT);
105 signed_bytes.extend_from_slice(&message);
106 self.signature = crate::signature::sign_hex(signing_key, &signed_bytes);
107 Ok(())
108 }
109
110 pub fn verify(&self) -> Result<()> {
116 let message = self.serialize_unsigned()?;
117 let mut signed_bytes = Vec::new();
118 signed_bytes.extend_from_slice(MEMBER_LIST_SIGNATURE_CONTEXT);
119 signed_bytes.extend_from_slice(&message);
120 crate::signature::verify_hex(&self.owner, &signed_bytes, &self.signature)
121 }
122
123 fn serialize_unsigned(&self) -> Result<Vec<u8>> {
125 let mut unsigned = self.clone();
126 unsigned.signature = String::new();
127 serde_json::to_vec(&unsigned).map_err(|error| SyncwebError::operation("failed to serialize member list", error))
128 }
129}
130
131#[derive(Clone, Debug, Serialize, Deserialize)]
133#[non_exhaustive]
134pub struct NetworkInfo {
135 pub name: String,
136 pub label: String,
137 pub created_at: u64,
138}
139
140pub async fn write_member_list(
151 docs: &DocsEngine,
152 blobs: &BlobStore,
153 doc: &Doc,
154 author: AuthorId,
155 signing: &SigningKey,
156 network: &Network,
157) -> Result<u64> {
158 let sequence = next_member_list_sequence(docs, blobs, doc).await?;
159 let mut list = SignedMemberList::from_network(network);
160 list.sequence = sequence;
161 list.sign(signing)?;
162 let bytes =
163 serde_json::to_vec(&list).map_err(|error| SyncwebError::operation("failed to serialize member list", error))?;
164 docs.set(doc, author, NETWORK_MEMBERS_KEY, bytes).await?;
165 Ok(sequence)
166}
167
168async fn next_member_list_sequence(docs: &DocsEngine, blobs: &BlobStore, doc: &Doc) -> Result<u64> {
170 let Some(entry) = docs.get_any(doc, NETWORK_MEMBERS_KEY).await? else {
171 return Ok(1);
172 };
173 let bytes = blobs.get(entry.content_hash()).await?;
174 let current: SignedMemberList = serde_json::from_slice(&bytes)
175 .map_err(|error| SyncwebError::operation("failed to deserialize member list", error))?;
176 Ok(current.sequence.saturating_add(1))
177}