1use crate::{crypto, types};
4
5pub fn authorize_recipient(
7 vault: &mut types::Vault,
8 murk: &mut types::Murk,
9 pubkey: &str,
10 name: Option<&str>,
11) -> Result<(), String> {
12 if crypto::parse_recipient(pubkey).is_err() {
13 return Err(format!("invalid public key: {pubkey}"));
14 }
15
16 if vault.recipients.contains(&pubkey.to_string()) {
17 return Err(format!("{pubkey} is already a recipient"));
18 }
19
20 vault.recipients.push(pubkey.into());
21
22 if let Some(n) = name {
23 murk.recipients.insert(pubkey.into(), n.into());
24 }
25
26 Ok(())
27}
28
29#[derive(Debug)]
31pub struct RevokeResult {
32 pub display_name: Option<String>,
34 pub exposed_keys: Vec<String>,
36}
37
38pub fn revoke_recipient(
41 vault: &mut types::Vault,
42 murk: &mut types::Murk,
43 recipient: &str,
44) -> Result<RevokeResult, String> {
45 let pubkey = if vault.recipients.contains(&recipient.to_string()) {
47 recipient.to_string()
48 } else {
49 murk.recipients
50 .iter()
51 .find(|(_, name)| name.as_str() == recipient)
52 .map(|(pk, _)| pk.clone())
53 .ok_or_else(|| format!("recipient not found: {recipient}"))?
54 };
55
56 if vault.recipients.len() == 1 {
57 return Err(
58 "cannot revoke last recipient — vault would become permanently inaccessible".into(),
59 );
60 }
61
62 vault.recipients.retain(|pk| pk != &pubkey);
63
64 let display_name = murk.recipients.remove(&pubkey);
65
66 for scoped_map in murk.scoped.values_mut() {
68 scoped_map.remove(&pubkey);
69 }
70 for entry in vault.secrets.values_mut() {
71 entry.scoped.remove(&pubkey);
72 }
73
74 let exposed_keys = vault.schema.keys().cloned().collect();
75
76 Ok(RevokeResult {
77 display_name,
78 exposed_keys,
79 })
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85 use crate::testutil::*;
86 use crate::types;
87 use std::collections::{BTreeMap, HashMap};
88
89 #[test]
90 fn authorize_recipient_success() {
91 let (_, pubkey) = generate_keypair();
92 let mut vault = empty_vault();
93 let mut murk = empty_murk();
94
95 let result = authorize_recipient(&mut vault, &mut murk, &pubkey, Some("alice"));
96 assert!(result.is_ok());
97 assert!(vault.recipients.contains(&pubkey));
98 assert_eq!(murk.recipients[&pubkey], "alice");
99 }
100
101 #[test]
102 fn authorize_recipient_no_name() {
103 let (_, pubkey) = generate_keypair();
104 let mut vault = empty_vault();
105 let mut murk = empty_murk();
106
107 authorize_recipient(&mut vault, &mut murk, &pubkey, None).unwrap();
108 assert!(vault.recipients.contains(&pubkey));
109 assert!(!murk.recipients.contains_key(&pubkey));
110 }
111
112 #[test]
113 fn authorize_recipient_duplicate_fails() {
114 let (_, pubkey) = generate_keypair();
115 let mut vault = empty_vault();
116 vault.recipients.push(pubkey.clone());
117 let mut murk = empty_murk();
118
119 let result = authorize_recipient(&mut vault, &mut murk, &pubkey, None);
120 assert!(result.is_err());
121 assert!(result.unwrap_err().contains("already a recipient"));
122 }
123
124 #[test]
125 fn authorize_recipient_invalid_key_fails() {
126 let mut vault = empty_vault();
127 let mut murk = empty_murk();
128
129 let result = authorize_recipient(&mut vault, &mut murk, "not-a-valid-key", None);
130 assert!(result.is_err());
131 assert!(result.unwrap_err().contains("invalid public key"));
132 }
133
134 #[test]
135 fn revoke_recipient_by_pubkey() {
136 let (_, pk1) = generate_keypair();
137 let (_, pk2) = generate_keypair();
138 let mut vault = empty_vault();
139 vault.recipients = vec![pk1.clone(), pk2.clone()];
140 vault.schema.insert(
141 "KEY".into(),
142 types::SchemaEntry {
143 description: String::new(),
144 example: None,
145 tags: vec![],
146 },
147 );
148 let mut murk = empty_murk();
149 murk.recipients.insert(pk2.clone(), "bob".into());
150
151 let result = revoke_recipient(&mut vault, &mut murk, &pk2).unwrap();
152 assert_eq!(result.display_name.as_deref(), Some("bob"));
153 assert!(!vault.recipients.contains(&pk2));
154 assert!(vault.recipients.contains(&pk1));
155 assert_eq!(result.exposed_keys, vec!["KEY"]);
156 }
157
158 #[test]
159 fn revoke_recipient_by_name() {
160 let (_, pk1) = generate_keypair();
161 let (_, pk2) = generate_keypair();
162 let mut vault = empty_vault();
163 vault.recipients = vec![pk1.clone(), pk2.clone()];
164 let mut murk = empty_murk();
165 murk.recipients.insert(pk2.clone(), "bob".into());
166
167 let result = revoke_recipient(&mut vault, &mut murk, "bob").unwrap();
168 assert_eq!(result.display_name.as_deref(), Some("bob"));
169 assert!(!vault.recipients.contains(&pk2));
170 }
171
172 #[test]
173 fn revoke_recipient_last_fails() {
174 let (_, pk) = generate_keypair();
175 let mut vault = empty_vault();
176 vault.recipients = vec![pk.clone()];
177 let mut murk = empty_murk();
178
179 let result = revoke_recipient(&mut vault, &mut murk, &pk);
180 assert!(result.is_err());
181 assert!(result.unwrap_err().contains("cannot revoke last recipient"));
182 }
183
184 #[test]
185 fn revoke_recipient_unknown_fails() {
186 let (_, pk) = generate_keypair();
187 let mut vault = empty_vault();
188 vault.recipients = vec![pk.clone()];
189 let mut murk = empty_murk();
190
191 let result = revoke_recipient(&mut vault, &mut murk, "nobody");
192 assert!(result.is_err());
193 assert!(result.unwrap_err().contains("recipient not found"));
194 }
195
196 #[test]
197 fn revoke_recipient_removes_scoped() {
198 let (_, pk1) = generate_keypair();
199 let (_, pk2) = generate_keypair();
200 let mut vault = empty_vault();
201 vault.recipients = vec![pk1.clone(), pk2.clone()];
202 vault.secrets.insert(
203 "KEY".into(),
204 types::SecretEntry {
205 shared: "ct".into(),
206 scoped: BTreeMap::from([(pk2.clone(), "scoped_ct".into())]),
207 },
208 );
209 let mut murk = empty_murk();
210 let mut scoped = HashMap::new();
211 scoped.insert(pk2.clone(), "scoped_val".into());
212 murk.scoped.insert("KEY".into(), scoped);
213
214 revoke_recipient(&mut vault, &mut murk, &pk2).unwrap();
215
216 assert!(vault.secrets["KEY"].scoped.is_empty());
217 assert!(murk.scoped["KEY"].is_empty());
218 }
219
220 #[test]
223 fn revoke_recipient_no_scoped() {
224 let (_, pk1) = generate_keypair();
225 let (_, pk2) = generate_keypair();
226 let mut vault = empty_vault();
227 vault.recipients = vec![pk1.clone(), pk2.clone()];
228 let mut murk = empty_murk();
229 murk.recipients.insert(pk2.clone(), "bob".into());
230
231 let result = revoke_recipient(&mut vault, &mut murk, &pk2).unwrap();
232 assert_eq!(result.display_name.as_deref(), Some("bob"));
233 assert!(!vault.recipients.contains(&pk2));
234 }
235}