1use crate::secrets_store_capnp::{self, secret_entry, secret_version_ref};
2use capnp::text_list;
3use chrono::{TimeZone, Utc};
4use serde::{Deserialize, Serialize};
5use std::cmp::Ordering;
6use std::collections::BTreeMap;
7use std::collections::HashMap;
8use std::fmt;
9use zeroize::Zeroize;
10mod command;
11mod config;
12mod event;
13mod zeroize_datetime;
14
15#[cfg(test)]
16mod tests;
17
18pub use command::*;
19pub use config::*;
20pub use event::*;
21pub use zeroize_datetime::*;
22
23pub const PROPERTY_USERNAME: &str = "username";
24pub const PROPERTY_PASSWORD: &str = "password";
25pub const PROPERTY_TOTP_URL: &str = "totpUrl";
26pub const PROPERTY_NOTES: &str = "notes";
27
28#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Zeroize)]
31#[zeroize(drop)]
32pub struct Status {
33 pub locked: bool,
34 pub unlocked_by: Option<Identity>,
35 pub autolock_at: Option<ZeroizeDateTime>,
36 pub version: String,
37 pub autolock_timeout: u64,
38}
39
40#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Zeroize)]
44#[zeroize(drop)]
45pub struct Identity {
46 pub id: String,
47 pub name: String,
48 pub email: String,
49 pub hidden: bool,
50}
51
52impl std::fmt::Display for Identity {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 write!(f, "{} <{}>", self.name, self.email)
55 }
56}
57
58#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
63#[serde(rename_all = "lowercase")]
64pub enum SecretType {
65 Login,
66 Note,
67 Licence,
68 Wlan,
69 Password,
70 #[serde(other)]
71 Other,
72}
73
74impl Zeroize for SecretType {
75 fn zeroize(&mut self) {
76 *self = SecretType::Other
77 }
78}
79
80impl SecretType {
81 pub fn password_properties(&self) -> &[&str] {
85 match self {
86 SecretType::Login => &[PROPERTY_PASSWORD],
87 SecretType::Note => &[],
88 SecretType::Licence => &[],
89 SecretType::Wlan => &[PROPERTY_PASSWORD],
90 SecretType::Password => &[PROPERTY_PASSWORD],
91 SecretType::Other => &[],
92 }
93 }
94
95 pub fn from_reader(api: secrets_store_capnp::SecretType) -> Self {
96 match api {
97 secrets_store_capnp::SecretType::Login => SecretType::Login,
98 secrets_store_capnp::SecretType::Licence => SecretType::Licence,
99 secrets_store_capnp::SecretType::Wlan => SecretType::Wlan,
100 secrets_store_capnp::SecretType::Note => SecretType::Note,
101 secrets_store_capnp::SecretType::Password => SecretType::Password,
102 secrets_store_capnp::SecretType::Other => SecretType::Other,
103 }
104 }
105
106 pub fn to_builder(self) -> secrets_store_capnp::SecretType {
107 match self {
108 SecretType::Login => secrets_store_capnp::SecretType::Login,
109 SecretType::Licence => secrets_store_capnp::SecretType::Licence,
110 SecretType::Note => secrets_store_capnp::SecretType::Note,
111 SecretType::Wlan => secrets_store_capnp::SecretType::Wlan,
112 SecretType::Password => secrets_store_capnp::SecretType::Password,
113 SecretType::Other => secrets_store_capnp::SecretType::Other,
114 }
115 }
116}
117
118impl fmt::Display for SecretType {
119 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
120 match self {
121 SecretType::Login => write!(f, "Login"),
122 SecretType::Note => write!(f, "Note"),
123 SecretType::Licence => write!(f, "Licence"),
124 SecretType::Wlan => write!(f, "WLAN"),
125 SecretType::Password => write!(f, "Password"),
126 SecretType::Other => write!(f, "Other"),
127 }
128 }
129}
130
131#[derive(Clone, Debug, Serialize, Deserialize, Default, PartialEq, Eq, Zeroize)]
138#[zeroize(drop)]
139pub struct SecretListFilter {
140 pub url: Option<String>,
141 pub tag: Option<String>,
142 #[serde(rename = "type")]
143 pub secret_type: Option<SecretType>,
144 pub name: Option<String>,
145 #[serde(default)]
146 pub deleted: bool,
147}
148
149#[derive(Clone, Debug, Serialize, Deserialize, Eq, Zeroize)]
158#[zeroize(drop)]
159pub struct SecretEntry {
160 pub id: String,
161 pub name: String,
162 #[serde(rename = "type")]
163 pub secret_type: SecretType,
164 pub tags: Vec<String>,
165 pub urls: Vec<String>,
166 pub timestamp: ZeroizeDateTime,
167 pub deleted: bool,
168}
169
170impl SecretEntry {
171 pub fn from_reader(reader: secret_entry::Reader) -> capnp::Result<Self> {
172 Ok(SecretEntry {
173 id: reader.get_id()?.to_string(),
174 timestamp: Utc.timestamp_millis_opt(reader.get_timestamp()).unwrap().into(),
175 name: reader.get_name()?.to_string(),
176 secret_type: SecretType::from_reader(reader.get_type()?),
177 tags: reader
178 .get_tags()?
179 .into_iter()
180 .map(|t| t.map(|t| t.to_string()))
181 .collect::<capnp::Result<Vec<String>>>()?,
182 urls: reader
183 .get_urls()?
184 .into_iter()
185 .map(|u| u.map(|u| u.to_string()))
186 .collect::<capnp::Result<Vec<String>>>()?,
187 deleted: reader.get_deleted(),
188 })
189 }
190
191 pub fn to_builder(&self, mut builder: secret_entry::Builder) {
192 builder.set_id(&self.id);
193 builder.set_timestamp(self.timestamp.timestamp_millis());
194 builder.set_name(&self.name);
195 builder.set_type(self.secret_type.to_builder());
196 let mut tags = builder.reborrow().init_tags(self.tags.len() as u32);
197 for (idx, tag) in self.tags.iter().enumerate() {
198 tags.set(idx as u32, tag)
199 }
200 let mut urls = builder.reborrow().init_urls(self.urls.len() as u32);
201 for (idx, url) in self.urls.iter().enumerate() {
202 urls.set(idx as u32, url)
203 }
204 builder.set_deleted(self.deleted);
205 }
206}
207
208impl Ord for SecretEntry {
209 fn cmp(&self, other: &Self) -> Ordering {
210 match self.name.cmp(&other.name) {
211 Ordering::Equal => self.id.cmp(&other.id),
212 ord => ord,
213 }
214 }
215}
216
217impl PartialOrd for SecretEntry {
218 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
219 Some(self.cmp(other))
220 }
221}
222
223impl PartialEq for SecretEntry {
224 fn eq(&self, other: &Self) -> bool {
225 self.id.eq(&other.id)
226 }
227}
228
229#[derive(Clone, Debug, Serialize, Deserialize, Eq, Zeroize)]
235#[zeroize(drop)]
236pub struct SecretEntryMatch {
237 pub entry: SecretEntry,
238 pub name_score: isize,
240 pub name_highlights: Vec<usize>,
242 pub url_highlights: Vec<usize>,
244 pub tags_highlights: Vec<usize>,
246}
247
248impl Ord for SecretEntryMatch {
249 fn cmp(&self, other: &Self) -> Ordering {
250 match other.name_score.cmp(&self.name_score) {
251 Ordering::Equal => self.entry.cmp(&other.entry),
252 ord => ord,
253 }
254 }
255}
256
257impl PartialOrd for SecretEntryMatch {
258 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
259 Some(self.cmp(other))
260 }
261}
262
263impl PartialEq for SecretEntryMatch {
264 fn eq(&self, other: &Self) -> bool {
265 self.entry.eq(&other.entry)
266 }
267}
268
269#[derive(Clone, Debug, Serialize, Deserialize, Default, PartialEq, Eq, Zeroize)]
273#[zeroize(drop)]
274pub struct SecretList {
275 pub all_tags: Vec<String>,
276 pub entries: Vec<SecretEntryMatch>,
277}
278
279#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
280#[serde(transparent)]
281pub struct SecretProperties(BTreeMap<String, String>);
282
283impl SecretProperties {
284 pub fn new(properties: BTreeMap<String, String>) -> Self {
285 SecretProperties(properties)
286 }
287
288 pub fn has_non_empty(&self, name: &str) -> bool {
289 matches!(self.0.get(name), Some(value) if !value.is_empty())
290 }
291
292 pub fn get(&self, name: &str) -> Option<&String> {
293 self.0.get(name)
294 }
295
296 pub fn len(&self) -> usize {
297 self.0.len()
298 }
299
300 pub fn is_empty(&self) -> bool {
301 self.0.is_empty()
302 }
303
304 pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
305 self.0.iter().map(|(k, v)| (k.as_str(), v.as_str()))
306 }
307}
308
309impl Drop for SecretProperties {
310 fn drop(&mut self) {
311 self.zeroize()
312 }
313}
314
315impl Zeroize for SecretProperties {
316 fn zeroize(&mut self) {
317 self.0.values_mut().for_each(Zeroize::zeroize);
318 }
319}
320
321#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Zeroize)]
328#[zeroize(drop)]
329pub struct SecretAttachment {
330 name: String,
331 mime_type: String,
332 content: Vec<u8>,
333}
334
335#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Zeroize)]
342#[zeroize(drop)]
343pub struct SecretVersion {
344 pub secret_id: String,
352 #[serde(rename = "type")]
354 pub secret_type: SecretType,
355 pub timestamp: ZeroizeDateTime,
358 pub name: String,
360 #[serde(default)]
362 pub tags: Vec<String>,
363 #[serde(default)]
366 pub urls: Vec<String>,
367 pub properties: SecretProperties,
370 #[serde(default)]
372 pub attachments: Vec<SecretAttachment>,
373 #[serde(default)]
379 pub deleted: bool,
380 #[serde(default)]
384 pub recipients: Vec<String>,
385}
386
387impl SecretVersion {
388 pub fn to_entry_builder(&self, mut builder: secret_entry::Builder) -> capnp::Result<()> {
389 builder.set_id(&self.secret_id);
390 builder.set_timestamp(self.timestamp.timestamp_millis());
391 builder.set_name(&self.name);
392 builder.set_type(self.secret_type.to_builder());
393 set_text_list(builder.reborrow().init_tags(self.tags.len() as u32), &self.tags)?;
394 set_text_list(builder.reborrow().init_urls(self.urls.len() as u32), &self.urls)?;
395 builder.set_deleted(self.deleted);
396 Ok(())
397 }
398}
399
400#[derive(Clone, Debug, Serialize, Deserialize, Zeroize)]
401#[zeroize(drop)]
402pub struct PasswordEstimate {
403 pub password: String,
404 pub inputs: Vec<String>,
405}
406
407#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Zeroize)]
408#[zeroize(drop)]
409pub struct PasswordStrength {
410 pub entropy: f64,
411 pub crack_time: f64,
412 pub crack_time_display: String,
413 pub score: u8,
414}
415
416#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Zeroize)]
417#[zeroize(drop)]
418pub struct SecretVersionRef {
419 pub block_id: String,
420 pub timestamp: ZeroizeDateTime,
421}
422
423impl SecretVersionRef {
424 pub fn from_reader(reader: secret_version_ref::Reader) -> capnp::Result<Self> {
425 Ok(SecretVersionRef {
426 block_id: reader.get_block_id()?.to_string(),
427 timestamp: Utc.timestamp_millis_opt(reader.get_timestamp()).unwrap().into(),
428 })
429 }
430
431 pub fn to_builder(&self, mut builder: secret_version_ref::Builder) {
432 builder.set_block_id(&self.block_id);
433 builder.set_timestamp(self.timestamp.timestamp_millis());
434 }
435}
436
437impl std::fmt::Display for SecretVersionRef {
438 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
439 write!(f, "{}", self.timestamp.format("%Y-%m-%d %H:%M:%S"))
440 }
441}
442
443#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
447pub struct Secret {
448 pub id: String,
449 #[serde(rename = "type")]
450 pub secret_type: SecretType,
451 pub current: SecretVersion,
452 pub current_block_id: String,
453 pub versions: Vec<SecretVersionRef>,
454 pub password_strengths: HashMap<String, PasswordStrength>,
455}
456
457impl Zeroize for Secret {
458 fn zeroize(&mut self) {
459 self.id.zeroize();
460 self.secret_type.zeroize();
461 self.current.zeroize();
462 self.current_block_id.zeroize();
463 self.versions.zeroize();
464 self.password_strengths.values_mut().for_each(Zeroize::zeroize);
465 }
466}
467
468impl Drop for Secret {
469 fn drop(&mut self) {
470 self.zeroize();
471 }
472}
473
474#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Zeroize)]
475#[zeroize(drop)]
476pub struct PasswordGeneratorCharsParam {
477 pub num_chars: u8,
478 pub include_uppers: bool,
479 pub include_numbers: bool,
480 pub include_symbols: bool,
481 pub require_upper: bool,
482 pub require_number: bool,
483 pub require_symbol: bool,
484 pub exclude_similar: bool,
485 pub exclude_ambiguous: bool,
486}
487
488#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Zeroize)]
489#[zeroize(drop)]
490pub struct PasswordGeneratorWordsParam {
491 pub num_words: u8,
492 pub delim: char,
493}
494
495#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Zeroize)]
496#[serde(rename_all = "lowercase")]
497#[zeroize(drop)]
498pub enum PasswordGeneratorParam {
499 Chars(PasswordGeneratorCharsParam),
500 Words(PasswordGeneratorWordsParam),
501}
502
503pub fn set_text_list<I, S>(mut text_list: text_list::Builder, texts: I) -> capnp::Result<()>
504where
505 I: IntoIterator<Item = S>,
506 S: AsRef<str>,
507{
508 for (idx, text) in texts.into_iter().enumerate() {
509 text_list.set(idx as u32, capnp::text::new_reader(text.as_ref().as_bytes())?);
510 }
511 Ok(())
512}
513
514#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Zeroize)]
515#[zeroize(drop)]
516pub struct ClipboardProviding {
517 pub store_name: String,
518 pub block_id: String,
519 pub secret_name: String,
520 pub property: String,
521}